当前位置: 首页 > news >正文

C++11新增关键字和范围for循环

C++11新增关键字和范围for循环

  • auto和decltype(c++11)
    • auto简介
    • auto使用
    • decltype使用
    • 范围for循环(C++11)
      • 范围for 循环使用
      • 范围for的使用条件
  • 指针空值nullptr(c++11)
  • default
  • delete

auto和decltype(c++11)

随着程序越来越复杂,程序中用到的类型也越来越复杂,经常体现在:

  1. 类型难于拼写。感受最明显的就是迭代器和函数指针。

  2. 含义不明确导致容易出错。

#include<iostream>
#include<string>
#include<vector>
#include<map>
using namespace std;int main() {map<string, string>dct = {{"insert","插入"},{"erase","删除"}};map<string, string>::iterator it = dct.begin();while (it != dct.end()) {cout << "[" << it->first << ":" << it->second << "]";++it;}return 0;
}

使用typedef的话,还会有新的问题:

#include<iostream>
using namespace std;typedef char* pstring;
int main() {//const pstring p1;//编译失败const char* p3;//可以编译通过const pstring* p2;//可以编译通过return 0;
}

typedefchar*取别名后,指针可以事先声明不用初始化,但会指向编译器初始化的随机值。

const pstring的本意是pstringchar*,在前面加const可以组成const char*,但实际上constpstring都只会修饰p1,这会使p1实际的类型是char* const(指向charconst指针),p1自带常属性,所以必须在定义时初始化,否则编译错误。

p3const char*const*前,修饰的是p3指向的内存空间,且p3指向哪里可以通过赋值决定,所以可以暂时不初始化。p2则是指向char* const的指针,自身不带常属性可以不先初始化。

所以随着对象的类型越来越长,需要在声明变量的时候清楚地知道表达式的类型。

auto简介

在早期c/c++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它,因为即使不用它,局部变量也是自动创建和自动销毁,而auto不能用于全局变量(生命周期和程序相同),导致auto在c++11之前又没有别的功能。

c++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。其中感受最明显的就是迭代器的自动推导

例如:

#include<iostream>
#include<vector>
using std::cout;
using std::endl;int TestAuto() {return 10;
}int main() {int a = 10;auto b = a;auto c = 'a';auto d = TestAuto();//输出对象的类型//typeid().name是将()内的类型推导成字符串。cout << typeid(b).name() << endl;cout << typeid(c).name() << endl;cout << typeid(d).name() << endl;//无法通过编译,使用auto定义变量时必须对其进行初始化//auto e;//初始化列表需要编译器支持c++11std::vector<int>arr = { 1,2,3,4,5,6,7 };//声明迭代器auto it1 = arr.begin();std::vector<int>::iterator it2= arr.begin();//it1和it2是同一类型,同一性质的变量if (it1 == it2)cout << "==";return 0;
}

auto使用

  1. auto与指针和引用结合起来使用

auto声明指针类型时,用autoauto*没有任何区别,但用auto声明引用类型时则必须加&

int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
  1. 在同一行定义多个变量

当用auto在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

例如:

 auto a = 1, b = 2; //可以auto c = 3, d = 4.0;  // 该行代码会编译失败,因为c和d的初始化表达式类型不同
  1. auto不能作为函数的参数

  2. auto不能直接用来声明数组

  3. 为了避免与c++98中的auto发生混淆,c++11只保留了auto作为类型指示符的用法。

    这意味着c++11的auto不再保留c++98的功能,c++98的auto和c++11的auto是2个完全不同的关键字。

  4. auto在实际中最常见的优势用法就是跟以后会讲到的c++11提供的新式for循环,还有lambda表达式等进行配合使用。

  5. auto也不是所有情况都好用,若auto推导出来的是复杂的类,会增加调试难度(比如类模板的模板参数是已定义的复杂类型,而auto推导出来的是另一个类型)。

    #include<iostream>
    using namespace std;int main() {auto pr = {1,3};pair<int, int> pr2 = { 1,3 };//class std::initializer_list<int>cout << typeid(pr).name() << endl;//struct std::pair<int,int>cout << typeid(pr2).name() << endl;return 0;
    }
    
  6. auto 会丢弃引用和 const 限定符(除非显式指定)。

    int main() {const int a = 10;auto b = a;  //int(const被丢弃)b = 3;auto& c = a; //const int&(显式保留引用和const)//c = 4;//c是const int&,不可被修改return 0;
    }
    
  7. auto 可以用于推导 std::initializer_list

    #include<iostream>
    using namespace std;int main() {auto il = { 1,2,3,4 };cout << typeid(il).name() << endl;return 0;
    }
    

    输出:

    class std::initializer_list<int>

decltype使用

decltype关键字能将变量的类型声明为表达式指定的类型。也可以用于推导声明的变量的类型。用来当成模板参数实例化类模板、函数模板同样适用。

#include<iostream>
#include<vector>
using std::cout;
using std::endl;
using std::vector;int main() {int i = 1;double d = 2.2;auto ret = i * d;//ret为double型decltype(ret) x;//声明double型对象xdecltype(ret) pi = 3.14;//定义double型对象picout << pi << endl;pi = 3.14159;cout << pi << endl;cout << typeid(pi).name() << endl;// 用ret的类型去实例化vector// 用来当做模板参数vector<decltype(ret)> v;v.push_back(1);v.push_back(1.1);for (auto e : v) cout << e << " "; cout << endl;cout << typeid(v).name() << endl;return 0;
}

范围for循环(C++11)

范围 for 循环是受到其他语言的影响,新增的语法。

比如Python的for循环:

for i in Container:print(i,sep=' ')

这个循环相当于将类似STL的容器Container中偶有的元素进行遍历。

范围for 循环使用

c++11中引入了基于范围的for循环。for循环后的括号由冒号:分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围

#include<iostream>
using std::cout;
using std::endl;int main() {int a[] = { 1,2,3,4,5,6,7,8 };for (int x : a)cout << x << ' ';cout << endl;for (auto x : a)cout << x << ' ';cout << endl;for (auto& x : a)//加引用,元素可修改cout << x << ' ';cout << endl;for (const auto& x : a)//加const,限制对x的修改cout << x << ' ';return 0;
}

与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。

auto前加const,仅用于限制通过符号来修改要遍历的底层数据,底层调用的迭代器是begin还是cbeginconst this指针的begin迭代器),取决于容器的对象是否用const修饰。

因此范围for不会主动选择const迭代器,除非容器对象本身是const

范围for的使用条件

  1. for循环迭代的范围必须是确定的

    对于数组而言,就是数组中第一个元素和最后一个元素的范围;

    对于类而言,应该提供beginend的方法,beginend就是for循环迭代的范围。

例如以下代码就有问题,因为for的范围不确定、

void TestFor(int array[]) {//array作为形参实际是一个int*指针for(auto& e : array)cout<< e <<endl;
}

且这个对外公开的函数接口只能是beginend,但凡有一个字母是大写都不行。

  1. 迭代的对象要实现++!=的操作。(++表示往下走,!=用于判断结尾)。

据说c++20有了Ranges库,可以简化部分条件,具体我也没研究过,这里重点写c++11。

范围for循环的本质就是通过迭代器来枚举所有元素。

#include<iostream>
#include<vector>
using namespace std;int main() {vector<int>a = { 2,7,1,8,2,8 };//范围for的本质是迭代器循环for (vector<int>::iterator it = a.begin(),ed=a.end(); it != ed; it++) {auto& tmp = *it;//操作*itcout << tmp << ' ';}cout << endl;for (auto& tmp : a)cout << tmp << ' ';cout << endl;return 0;
}

在之前的string的模拟实现、vector的模拟实现、list的模拟实现、红黑树的模拟实现和哈希相关的模拟实现都支持范围for循环。

例如,这里随便设计一个vector。这个vector提供了beginend接口,指针支持++!=的操作,所以它支持范围for循环。

#include<iostream>
#include<initializer_list>
using namespace std;namespace mystd {template<class T>class vector {public:typedef T* iterator;typedef const T* const_iterator;//初始化列表为形参的构造函数vector(initializer_list<T> il) {_arr = new T[il.size()];_size = _capacity = il.size();iterator itv = begin();typename initializer_list<T>::iterator iti = il.begin();while (iti != il.end()) {*itv++ = *iti++;}}//析构函数~vector() {delete[] _arr;_size = _capacity = 0;}//迭代器//提供begin和end接口iterator begin() {return _arr;}iterator end() {return _arr + _size;}//提供begin和end的带const的重载,因为对象可能是const对象const_iterator begin() const {return _arr;}const_iterator end() const {return _arr + _size;}private:T* _arr;size_t _size;size_t _capacity;};
}int main() {//推导为iteratormystd::vector<int>a = { 3,1,4,1,5,9 };for (auto& x : a)cout << x << ' ';cout << endl;//推导为const_iteratorconst mystd::vector<int>a2 = { 3,1,4,1,5,9 };for (auto& x : a2)cout << x << ' ';cout << endl;return 0;
}

指针空值nullptr(c++11)

未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

void TestPtr(){int* p1 = NULL;int* p2 = 0;
}

NULL实际是一个宏,在传统的c头文件(stddef.h)中,可以看到如下代码:

#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

NULL的使用仍然有限制,比如这个代码:

#include<iostream>
using std::cout;
using std::endl;void f(int a) {cout << "f(int)" << endl;
}void f(int* a) {cout << "f(int*)" << endl;
}int main() {f(0);f(NULL);//这个函数调用的是哪一个?f((int*)NULL);return 0;
}

输出:

f(int)
f(int)
f(int*)

在c++98中,字面常量 0 既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整型常量,所以第2个函数用的是f(int)

如果要将其按照指针方式来使用,必须对其进行强转(void*)0。(c++会把NULL看成0,c语言看成(void*)0)。

因此c++11引入nullptr作为c++的空指针在某些场合代替宏NULL

用来补c++的坑,我的评价是旧bug不敢改就加新bug,新bug在N多年后又变成旧bug,之后的人们又会添加怎样的新bug就不清楚了。

注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是c++11作为新关键字引入的。

  2. 在c++11中,sizeof(nullptr)sizeof((void*)0)所占的字节数相同。

  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr

default

某些情况使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include"mystd.h"
using namespace std;class Person {
public:Person(const char* name = "", int age = 0):_name(name), _age(age) {}//拷贝构造Person(const Person& p):_name(p._name), _age(p._age) {}//强制生成默认的移动构造函数Person(Person&& p) = default;~Person() {}
private:mystd::string _name;int _age;
};
int main() {Person s1 = { "shawshank",666 };Person s3 = std::move(s1);return 0;
}

delete

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

delete原本的用处是释放通过new申请的空间。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include"mystd.h"
using namespace std;class Person {
public:Person(const char* name = "", int age = 0):_name(name), _age(age) {}//拷贝构造Person(const Person& p):_name(p._name), _age(p._age) {}//强制删除移动构造Person(Person&& p) = delete;~Person() {}
private:mystd::string _name;int _age;
};
int main() {Person s1 = { "shawshank",666 };//Person s3 = std::move(s1);//无法调用被删除的函数return 0;
}

此外,在c++11,新增新的关键字final,经过final修饰的类是最终类,同样无法被继承。final修饰虚函数,表示该虚函数不能再被重写

override则是修饰派生类,检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。所以override可以规范程序员的行为,让程序员不忘记重写虚函数。

关于这2个关键字的解释,详见类与对象—继承-CSDN博客和类和对象—多态-CSDN博客。

http://www.lryc.cn/news/619136.html

相关文章:

  • Flutter ExpansionPanel组件(可收缩的列表)
  • Qt中定时器介绍和使用
  • Gradle(二)Gradle的优势、项目结构介绍
  • python2操作neo4j
  • HTTPS加密与私有CA配置全攻略
  • spring-cloud整合nacos详细攻略
  • 读《精益数据分析》:UGC平台的数据指标梳理
  • 11-docker单机版的容器编排工具docker-compose基本使用
  • 数据分析专栏记录之 -基础数学与统计知识
  • Threejs 设置灯光照射点位置 辅助器不跟随移动
  • 大数据中的数据压缩原理
  • QT第五讲-控件QLineEdit、QSpinBox、QSlider、QScrollBar、QDial、QProgressBar、QLCDNumber
  • 计算机网络摘星题库800题笔记 第4章 网络层
  • 前端最新Vue2+Vue3基础入门到实战项目全套教程,自学前端vue就选黑马程序员,一套全通关!笔记
  • MCU中的液晶显示屏LCD(Liquid Crystal Display)控制器
  • VUE的8个生命周期
  • C++list(2)
  • 【JavaEE】多线程之线程安全(上)
  • 串口通信学习
  • 【PyTorch学习笔记 - 03】 Transforms
  • Spring-Cache 缓存数据
  • Dubbo 3.x源码(33)—Dubbo Consumer接收服务调用响应
  • 赛灵思ZYNQ官方文档UG585自学翻译笔记:UART Controller,通用异步收发传输器控制器
  • I2C 接收与发送数据的流程
  • 成都影像产业园实训考察:重庆五一职院关注技能就业
  • 【DL】深层神经网络
  • 《疯狂Java讲义(第3版)》学习笔记ch1
  • 力扣 hot100 Day71
  • 【1】Transformers快速入门:自然语言处理(NLP)是啥?
  • 机器学习第十课之TF-IDF算法(红楼梦文本分析)