c++11标准对于语言核心部分做了相当大的改动。主要目的有若干个:
当然,c++11对于标准库、STL和泛型的扩充也绝对不可小觑,但这些变更主要是为了配合语言核心的变更。比如,为支持右值引用带来的对象所有权的流转,引入了move算法--这在数学意义上也是对于代数完备性的一个有力补充,更不用说由此带来的可观存储效率的提升了。
还有新引入的三种智能指针和四种无序关联容器、字符串和数值类型互相转换的工具函数,以及新引入的若干针对标准容器的小改进,如顺序容器的常量起始和终止迭代器以及可以直接插入值而不必再构造临时变量的emplace函数族,等等。
c++11 的新特性
page:31
c++ 11重新定义了long long 类型必须大于等于64bit。 这似乎看起来并不是一个什么新的特性,因为c++11之前,各个平台的编译器也自己实现了类型 long long,只是没有明确的标准来统一。所以这个特性对于我们写代码来说,不会有太多区别。
《c++程序设计语言》 6.3.5
初始化器就是对象在初始化状态下被赋予的值。初始化器有4中可能的形式:
X a1 {v};
X a2 = {v};
X a3 = v;
X a4(v);
在这些形式中,只有第一种不受任何限制,在所有场景中都能使用。我强烈建议程序员使用这种方式为变量赋初值,它含义清晰,与其他形式相比不太容易出错。不过,第一种初值形式(a1)在c++11新标准中刚刚被踢出,因此在老代码中使用的都是后面三种形式。其中,使用=的两种形式是从c语言继承而来的。使用{}的初始化称为列表初始化(list initialization),它能防止窄化转换。
demo:
#include
int main(){
// 1.0 列表初始化,内置类型提供默认值,
char *p {}; // 默认值是 nullptr
long long a = {}; // 默认0
bool bl ={}; // 默认false
// 2.0 将会是未知数值。
long long b;
// 3.0 窄化转换
double db {12.123};
int db2 {db};// 编译器可以检测出错误,警告。发生截断(窄化转换)
int db3 = db; // 编译器没有提示,发生窄化转换
std::cout<
当我们使用auto 关键字从初始化器推断变量的类型时,没必要采用列表初始化的方式。而且如果初始化器是{}列表,则推断得到的数据类型肯定不是我们想要的结构。当使用auto的时候,应该选择=的初始化形式。
auto z1{99}; // z1的类型时 initizlizer_list
auto z2 = 99; // z2的类型时 int
当我们使用{}符号进行初始化时,不会进行窄化转换(实际操作在g++中是编译器给出警告),在使用auto的语句中,{}列表初始化的类型被推断为std::initializer_list
NULL 是一个宏定义,预处理为 0 。 这是一个整数,所以在函数调用过程中如果作为参数的化,可能产生二义性,NULL 作为实参就可能被当做实参类型为int,而nullptr则不会,nullptr的类型就是一个指针。
nullptr 为一个常量,代表空指针。
所以尽量使用nullptr来作为空指针。
constexpr变量
定义:常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。
用法:c++11 新标准规定,允许将变量申明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。 (明确指出程序员的意图,让编译器来检测确保它一定是一个常量值)
建议:一般来说,如果你认定变量是一个常量表达式,那就把它声明成 constexpr类型。
constexpr函数
定义:constexpr函数是指能用于常量表达式的函数。
定义constexptr函数的方法与其他函数类似,不过要遵循几项约定:函数的返回类型及所有形参的类型都是字面值类型,而且函数体中必须有且只有一条return语句:
constexpr int new_sz() { reutrn 42;}
constexpr int foo = new_sz(); // 正确:foo是一个常量表达式。
执行该任务时,编译器把对constexpr函数的调用替换成结果值。为了能在编译过程中随时展开,constexpr函数被隐式地制定为内敛函数。
内敛函数和constexpr函数可以再程序中定义多次。毕竟,编译器想要展开函数仅有函数申明是不够的,还需要函数的定义。不过,对于某个给定的内敛函数或者constexptr函数来说,它的定义必须完全一致。基于这个原因,内敛函数和constexprhan函数通常定义在头文件中。
constexpr函数不一定返回常量表达式:
#include
constexpr size_t scale(size_t cnt){return 12*cnt;}
int main(){
int arr[scale(2)];
int i =2;
// 需要编译阶段确定scale(i)的值,所以这里是错误的,但是gcc 并没有报错也没有警告?? 什么情况
int a2[scale(i)];
//运行阶段确定 scale(i)的值。
std::cout<
can@ubuntu:~/work/c++$ g++ -std=c++11 constexpr.cpp -Wall
can@ubuntu:~/work/c++$ ./a.out
24
arr: 24
a2: 1
can@ubuntu:~/work/c++$ g++ --version
g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
书《c++程序设计语言》2.2.3
constexpr函数可以接受非常量实参,但此时结果将不会是一个常量表达式。当程序的上下文不需要常量表达式时,我们可以使用非常量表达式实参来调用constexpr函数,这样我们就不用把同一个函数定义两次了:其中一个用于常量表达式,一个用于变量。
个人总结: 对于constexpr 变量,编译器会检查确保这个值能在编译期间确定值。 但是如果定义了一个constexpr变量,想让这个变量是通过一个函数计算得到的,那么被使用的这个函数就必须是constexpr函数。 constexpr函数并不会要求它的计算结果是一定在编译阶段确定的,它可以复用,在用于计算constexpr变量时,编译器会检查计算结果确保是编译期间确定,在用于普通的运行时确定值时,它一样遵循普通的函数调用,在运行时确定返回值。所以constexpr函数的“constexpr特性”只是在被constexpr变量使用时才体现。 这也就是 constexpr函数是指能用于常量表达式的函数 这句话的意思。
constexpr 构造函数
7.5.6 尽管构造函数不能是const的,但是字面常量类的构造函数可以是constexptr函数。constexpr构造函数可以声明成=default的形式或者是删除的形式。否则,constexpr构造函数就必须既符合构造函数的要求(意味着不能包含返回语句),又符合constexpr函数的要求(意味着它能拥有的唯一可执行语句就是返回语句),综合这两点, (很矛盾,所以应该说constexptr构造函数是个特例),constexpr构造函数体一般来说应该是空的。constexpr构造函数必须初始化所有数据成员,初始值只能用constexpr构造函数或者常量表达式。
传统的方法使用typdef,新标准使用using
using MY_DATA = struct _data; //
decltype(f()) sum = x; // sum的类型就是函数f的返回类型
int i =0;
decltype((i)) d = i; // decltype的表达式如果是加上了括号的变量,结果将是引用, d是int&,必须初始化
c++ 11新标准规定,可以为数据成员提供一个类内初始值。创建对象时,类内初始值将用于初始化数据成员。
class Dog{
private: int age = 1;
}
//输出 str中的每一个字符, 如果要修改其中的字符,则 应该 在for里面使用 auto &c,引用类型。
string str("some string");
for( auto c : str){
cout << c << endl;
}
如果我们定义了带参数的构造函数,编译器就不会自动给类定义默认的无参构造函数,如果代码中又确实需要无参的默认构造函数,这个时候可以用 =default 让编译器生成默认构造函数,非常方便。
class Sales_data{
public:
// 非委托构造汉纳树使用对应的实参初始化成员
Sales_data( std::string s, unsigend cnt, double price):
bookNo(s), units_sold(cnt), revence(cnt*price){
}
// 其余构造函数全部委托给另一个构造函数
Sales_data(): Sales_data("", 0, 0){}; // 默认构造函数,被委托给了第一个三参数函数
Sales_data(std::string s): Sales_data(s,0,0){};//
Sales_data(std:::istream &is) Sales_data(){ //该函数委托给了默认构造函数,而默认构造函数再委托给三参数函数
read(is, *this);
}
}
可调用对象
到目前为止,我们使用过的仅有的两种可调用对象时函数和函数指针。还有其他两种可调用对象:重载了函数调用运算符的类,以及lambda表达式。
lambda 表达式表示一个可调用的代码单元。lamba具有一个返回类型,一个参数列表和一个函数体,与函数不同的是,lambda可能定义再函数内部。
auto compare = [=](const string &s){ return s.size() >= sz; };
find_if(words.begin(),words.end(),compare);
函数适配器,特点就是可以再调用的时候,一次绑定参数关系,可以事先绑定固定的参数,也可以重排原来函数的参数顺序。
auto newCallable = bind(callable, arg_list);
其中,newCallable本身是一个可调用对象,arg_list 是一个逗号分隔的参数列表,对应给定的callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。
shared_ptr
unique_ptr
class Animal{
public :
Animal() = default;
virtual ~Animal() = default;
virtual void eat() = 0;
};
class Dog : public Animal{
public:
void eat() override{
std::cout<<"dog eat"<
.........其他。。。。。。。。。。。。。。