constexpr 关键字的功能是使指定的常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数。
注意,获得在编译阶段计算出结果的能力,并不代表 constexpr 修饰的表达式一定会在程序编译阶段被执行,具体的计算时机还是编译器说了算。
1)修饰普通变量
#includeusing namespace std; int main(){ constexpr int num = 1+2; //使用const也能通过 //必须满足num是常量且使用常量表达式为其初始化 int url[num] = {1,2,3}; //如果上一行没加constexpr就会报错 cout< 2)修饰函数
constexpr 还可以用于修饰函数的返回值,这样的函数又称为“常量表达式函数”,但必须满足下面4个条件:
- 整个函数的函数体中,除了可以包含 using 指令、typedef 语句以及 static_assert 断言外,只能包含一条 return 返回语句
- 该函数必须有返回值,即函数的返回值类型不能是 void
- 函数在使用之前,必须有对应的定义语句(不同于普通函数声明后就可以使用,在调用后定义也行)
- return 返回的表达式必须是常量表达式,且不能包含赋值的操作
3)修饰类的构造函数
#includeusing namespace std; struct myType{ //想自定义一个可产生常量的类型时,正确的做法是在该类型的内部添加一个常量构造函数 constexpr myType(char *name, int age):name(name),age(age){}; //constexpr 修饰类的构造函数时,要求该构造函数的函数体必须为空,且采用初始化列表的方式为各个成员赋值时,必须使用常量表达式 const char* name; int age; }; int main(){ constexpr struct myType mt{"zhangsan", 10}; cout< 4)修饰模板函数
C++11 语法中,constexpr 可以修饰模板函数,但如果 constexpr 修饰的模板函数实例化结果不满足常量表达式函数的要求,则 constexpr 会被自动忽略,即该函数就等同于一个普通函数。
#include12. long long 超长整型using namespace std; struct myType { const char* name; int age; //其它结构体成员 }; template constexpr T display(T t){ return t; } int main(){ struct myType stu{"zhangsan", 10}; struct myType stu1 = display(stu); //由于myType结构体中没有对应的常量构造函数,所以实例化后的函数不是常量表达式函数,此时 constexpr 是无效的 constexpr int num = display(10); //模板函数的类型 T 为 int 类型,实例化后的函数符合常量表达式函数的要求,所以该函数的返回值就是一个常量表达式 return 0; } 如同 long 类型整数需明确标注 “L” 或者 “l” 后缀一样,要使用 long long 类型的整数,也必须标注对应的后缀,如果不加任何标识会默认为int类型:
- 对于有符号 long long 整形,后缀用 “LL” 或者 “ll” 标识。例如,“10LL” 就表示有符号超长整数 10;
- 对于无符号 long long 整形,后缀用 “ULL”、“ull”、“Ull” 或者 “uLL” 标识。例如,“10ULL” 就表示无符号超长整数 10;
对于 long long 类型来说,如果想了解当前平台上 long long 整形的取值范围,可以使用
头文件中与 long long 整形相关的 3 个宏,分别为 LLONG_MIN、LLONG_MAX 和 ULLONG_MIN: 13. 右值引用
LLONG_MIN:代表当前平台上最小的 long long 类型整数;
LLONG_MAX:代表当前平台上最大的 long long 类型整数;
ULLONG_MAX:代表当前平台上最大的 unsigned long long 类型整数(无符号超长整型的最小值为 0);
大部分场景下,判断某个表达式是左值还是右值:
- 可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值
- 有名称的、可以获取到存储地址的表达式即为左值;反之则是右值
//C++98/03 int num = 10; int &b = num; //允许为num左值建立一个引用 //int &c = 10; //错误,不支持为右值建立非常量左值引用 const int &d = num; const int &e = 10; //允许使用常量左值引用操作右值右值往往是没有名称的,因此要使用它只能借助引用的方式。这就产生一个问题,实际开发中我们可能需要对右值进行修改(实现移动语义时就需要),显然左值引用的方式是行不通的。为此,C++11 标准新引入了另一种引用方式,称为右值引用,用 “&&” 表示。
和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化
int num = 10; // int &&a = num; //右值引用不能初始化为左值 int &&a = 10; cout< 14. 引用限定符左值和右值的区分也同样适用于类对象,本节中将左值的类对象称为左值对象,将右值的类对象称为右值对象。默认情况下,对于类中用 public 修饰的成员函数,既可以被左值对象调用,也可以被右值对象调用。
某些场景中,我们可能需要限制调用成员函数的对象的类型(左值还是右值),为此 C++11 新添加了引用限定符。所谓引用限定符,就是在成员函数的后面添加 “&” 或者 “&&”,从而限制调用者的类型(左值还是右值)。
引用限定符不适用于静态成员函数和友元函数。
#includeusing namespace std; class demo1{ public: demo1(int num):m_num(num){} int get_num(){return this->m_num;} private: int m_num; } class demo2{ public: demo2(int num):m_num(num){} int get_num() & {return this->m_num;}//添加一个"&”来限制调用者是左值 private: int m_num; } int main(){ demo1 a(10); cout< const和引用限定符修饰类的成员函数时,都位于函数的末尾。C++11 标准规定,当引用限定符和 const 修饰同一个类的成员函数时,const 必须位于引用限定符前面。当 const && 修饰类的成员函数时,调用它的对象只能是右值对象;当 const & 修饰类的成员函数时,调用它的对象既可以是左值对象,也可以是右值对象。无论是 const && 还是 const & 限定的成员函数,内部都不允许对当前对象做修改操作。
#include15. nullptrusing namespace std; class demo { public: demo(int num,int num2) :num(num),num2(num2) {} //左值和右值对象都可以调用 int get_num() const &{return this->num;} //仅供右值对象调用 int get_num2() const && {return this->num2;} private: int num; int num2; }; int main() { demo a(10,20); cout << a.get_num() << endl; // 正确 cout << move(a).get_num() << endl; // 正确 //cout << a.get_num2() << endl; // 错误 cout << move(a).get_num2() << endl; // 正确 return 0; } 所谓“野指针”,又称“悬挂指针”,指的是没有明确指向的指针。野指针往往指向的是那些不可用的内存区域,这就意味着像操作普通指针那样使用野指针(例如 &p),极可能导致程序发生异常。
实际开发中,避免产生“野指针”最有效的方法,就是在定义指针的同时完成初始化操作,即便该指针的指向尚未明确,也要将其初始化为空指针。
//C++98/03标准中,初始化空指针的两种方式 int *p = 0; //将指针明确指向 0(0x0000 0000)这个内存空间,另一方面,大多数操作系统都不允许用户对地址为 0 的内存空间执行写操作 int *p = NULL; //NULL 并不是 C++ 的关键字,它是 C++ 为我们事先定义好的一个宏,并且它的值往往就是字面量 0(#define NULL 0)个别情况下,将指针赋值为NULL会导致程序运行和预取不符合:
#includeusing namespace std; void isnull(void *c){ cout << "void*c" << endl; } void isnull(int n){ cout << "int n" << endl; } int main() { isnull(0); isnull(NULL); //期望调用isnull(void *c),结果是isnull(int n) return 0; //输出结果都是 int n } nullptr 是 nullptr_t 类型的右值常量,专用于初始化空类型指针。nullptr_t 是 C++11 新增加的数据类型,可称为“指针空值类型”。也就是说,nullptr 仅是该类型的一个实例对象(已经定义好,可以直接使用)。
//nullptr 可以被隐式转换成任意的指针类型 int * a1 = nullptr; char * a2 = nullptr; //nullptr 无法隐式转换为整形,而可以隐式匹配指针类型16. shared_ptr智能指针在实际的 C++ 开发中,我们经常会遇到诸如程序运行中突然崩溃、程序运行所用内存越来越多最终不得不重启等问题,这些问题往往都是内存资源管理不当造成的。比如:
- 有些内存资源已经被释放,但指向它的指针并没有改变指向(成为了野指针),并且后续还在使用
- 有些内存资源已经被释放,后期又试图再释放一次(重复释放同一块内存会导致程序运行崩溃)
- 没有及时释放不再使用的内存资源,造成内存泄漏,程序占用的内存资源越来越多
C++98/03 标准中,支持使用 auto_ptr 智能指针来实现堆内存的自动回收;C++11 新标准在废弃 auto_ptr 的同时,增添了 unique_ptr、``shared_ptr以及weak_ptr` 这 3 个智能指针来实现堆内存的自动回收。智能指针可以在适当时机自动释放分配的内存。
C++ 智能指针底层是采用引用计数的方式实现的。简单的理解,智能指针在申请堆内存空间的同时,会为其配备一个整形值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。当堆空间对应的整形值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉。
和 unique_ptr、weak_ptr 不同之处在于,多个 shared_ptr 智能指针可以共同使用同一块堆内存。并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针(只有引用计数为 0 时,堆内存才会被自动释放)。
1)创建
std::shared_ptrp1; //不传入任何实参 std::shared_ptr p2(nullptr); //传入空指针nullptr std::shared_ptr p3(new int(10)); //向一块存有 10 这个 int 类型数据的堆内存空间 std::shared_ptr p4 = std::make_shared (10); //提供std::make_shared 模板函数进行初始化 std::shared_ptr p5(p4); //拷贝构造函数 std::shared_ptr p6(std::move(p5)); //移动构造函数 //同一普通指针不能为多个shared_ptr对象赋值 int *ptr = new int; std::shared_ptr p1(ptr); //std::shared_ptr p2(ptr); //错误 //shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存 std::shared_ptr p7(new int[10], std::default_delete ()); std::shared_ptr p8(new int[10], [](int* p){delete[] p;}); 2)shared_ptr模板类提供的常用成员方法:
成员方法名 功 能 operator=() 重载赋值号,使得同一类型的 shared_ptr 智能指针可以相互赋值。 operator*() 重载 * 号,获取当前 shared_ptr 智能指针对象指向的数据。 operator->() 重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。 swap() 交换 2 个相同类型 shared_ptr 智能指针的内容。 reset() 当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。 get() 获得 shared_ptr 对象内部包含的普通指针。 use_count() 返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。 unique() 判断当前 shared_ptr 对象指向的堆内存,是否不再有其它 shared_ptr 对象再指向它。 operator bool() 判断当前 shared_ptr 对象是否为空智能指针,如果是空指针,返回 false;反之,返回 true。 除此之外,C++11 标准还支持同一类型的 shared_ptr 对象,或者 shared_ptr 和 nullptr 之间,进行 ==,!=,<,<=,>,>= 运算。
#include17. weak_ptr智能指针#include using namespace std; int main(){ std::shared_ptr p1(new int(10)); sht::shared_ptr p2(p1); cout<<*p2< cout << "p1 不为空" << endl; } else { cout << "p1 为空" << endl; } //以上操作,并不会影响 p2 cout << *p2 << endl; //输出:10 //判断当前和 p2 同指向的智能指针有多少个 cout << p2.use_count() << endl; //输出:1 return 0; } C++11标准虽然将 weak_ptr 定位为智能指针的一种,但该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用。
当 weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数。
除此之外,weak_ptr 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。
1)创建
std::weak_ptrwp1; //空weak_ptr指针 std::weak_ptr wp2(wp1); //若 wp1 为空指针,则 wp2 也为空指针;反之,如果 wp1 指向某一 shared_ptr 指针拥有的堆内存,则 wp2 也指向该块存储空间(可以访问,但无所有权) std::shared_ptr sp(new int); std::weak_ptr wp3(sp); //利用已有的 shared_ptr 指针为其初始化 2)weak_ptr模板类提供的可调用的成员方法:
成员方法 功 能 operator=() 重载 = 赋值运算符,是的 weak_ptr 指针可以直接被 weak_ptr 或者 shared_ptr 类型指针赋值。 swap(x) 其中 x 表示一个同类型的 weak_ptr 类型指针,该函数可以互换 2 个同类型 weak_ptr 指针的内容。 reset() 将当前 weak_ptr 指针置为空指针。 use_count() 查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。 expired() 判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)。 lock() 如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。 #include18. unique_ptr智能指针#include using namespace std; int main(){ std::shared_ptr sp1(new int(10)); std::shared_ptr sp2(sp1); std::weak_ptr wp(sp2); //输出和 wp 同指向的 shared_ptr 类型指针的数量 cout << wp.use_count() << endl; //输出:2 //释放 sp2 sp2.reset(); cout << wp.use_count() << endl; //输出:1 //借助 lock() 函数,返回一个和 wp 同指向的 shared_ptr 类型指针,获取其存储的数据 cout << *(wp.lock()) << endl; //输出:10 return 0; } 作为智能指针的一种,unique_ptr 指针自然也具备“在适当时机自动释放堆内存空间”的能力。和 shared_ptr 指针最大的不同之处在于,unique_ptr 指针指向的堆内存无法同其它 unique_ptr 共享,也就是说,每个 unique_ptr 指针都独自拥有对其所指堆内存空间的所有权。这也就意味着,每个 unique_ptr 指针指向的堆内存空间的引用计数,都只能为 1,一旦该 unique_ptr 指针放弃对所指堆内存空间的所有权,则该空间会被立即释放回收。
1)创建
std::unique_ptrp1(); std::unique_ptr p2(nullptr); std::unique_ptr p3(new int); //std::unique_ptr p4(p3);//错误,堆内存不共享,没有提供拷贝构造函数 std::unique_ptr p4(std::move(p3));//正确,调用移动构造函数 //自定义释放规则 struct myDel{ //unique_ptr 自定义释放规则,只能采用函数对象的方式 void operator()(int *p) { delete p; } }; std::unique_ptr p6(new int); 2)常用成员函数:
成员函数名 功 能 operator*() 获取当前 unique_ptr 指针指向的数据。 operator->() 重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。 operator =() 重载了 = 赋值号,从而可以将 nullptr 或者一个右值 unique_ptr 指针直接赋值给当前同类型的 unique_ptr 指针。 operator 重载了 [] 运算符,当 unique_ptr 指针指向一个数组时,可以直接通过 [] 获取指定下标位置处的数据。 get() 获取当前 unique_ptr 指针内部包含的普通指针。 get_deleter() 获取当前 unique_ptr 指针释放堆内存空间所用的规则。 operator bool() unique_ptr 指针可直接作为 if 语句的判断条件,以判断该指针是否为空,如果为空,则为 false;反之为 true。 release() 释放当前 unique_ptr 指针对所指堆内存的所有权,但该存储空间并不会被销毁。 reset§ 其中 p 表示一个普通指针,如果 p 为 nullptr,则当前 unique_ptr 也变成空指针;反之,则该函数会释放当前 unique_ptr 指针指向的堆内存(如果有),然后获取 p 所指堆内存的所有权(p 为 nullptr)。 swap(x) 交换当前 unique_ptr 指针和同类型的 x 指针。 #include参考#include using namespace std; int main(){ std::unique_ptr p5(new int); *p5 = 10; // p 接收 p5 释放的堆内存 int * p = p5.release(); cout << *p << endl; //输出:10 //判断 p5 是否为空指针 if (p5) { cout << "p5 is not nullptr" << endl; } else { cout << "p5 is nullptr" << endl; //输出 } std::unique_ptr p6; //p6 获取 p 的所有权 p6.reset(p); cout << *p6 << endl; //输出:10 return 0; } 1.http://c.biancheng.net/cplus/11/