1.auto & decltype
auto:让编译器在编译期就推导出变量的类型,可以通过=右边的类型推导出变量的类型
auto a = 10 //10是int类型自动被推导出来
auto的”自动推导“能力只能用在”初始化“的场合
具体来说,就是赋值初始化或者花括号初始化(初始化列表),变量右边必须要有一个表达式,这样你才能在左边放上auto,编译器才能找到表达式,帮你自动计算类型
在类成员变量初始化的时候,目前C++标准不允许使用auto推导类型
auto总是可以推导出值类型,绝不会是引用,auto可以附上const,volatile,*,&这样的类型修饰符,得到新的类型
decltype:相对于auto用于推导类型变量,而decltype则用于推导表达式类型,这里只用于编译器分析表达式类型,表达式实际不会进行运算
const int& i = 1; decltype(i) b = 2; //b是const int&
2.nullptr
nullptr是C++11用来表示空指针新引入的常量值,再C++中如果表示空指针语义时建议使用nullptr而不要使用NULL,因为NULL本质上是一个int型的0,而不是一个指针
void func(void *ptr) {
cout << "func ptr" << endl;
}
void func(int i) {
cout << "func i" << endl;
}
void main() {
func(NULL);//输出func i
func(nullptr);//输出func ptr
}
3.final & override
final用于修饰一个类,表示禁止该类进一步派生和虚函数进一步重载
class base final{
virtual void func() {
cout << "base" << endl;
}
};
class Derived : base {
void func() override {
cout << "derived" << endl;
}
};
override用于修饰派生类中的成员函数,标明该函数重写了基类函数,如果一个函数声明了override但父类却没有这个虚函数,编译报错,使用override关键字可以避免开发者再重写基类函数时无意产生的错误
class Derived : base {
void func() override {
cout << "derived" << endl;
}
void func1() override {} //基类没有定义func1(),不可以被重写
};
4.default & delete
c++11引入了default特性,多数时候用于声明构造函数为默认构造函数,如果类中有了自定义的构造函数,编译器就不会生成默认构造函数
class A {
public:
A(int i) { a = i; }
int a;
};
void main() {
A a; //编译出错
}
上述代码编译出错,因为没有匹配的构造函数,编译器没有生成默认的构造函数,而程序员只需在函数声明后加上 = default 就可将函数声明为defaulted函数,编译器将为显式声明的defaulted函数自动生成函数体
class A {
public:
A() = default;
A(int i) { a = i; }
int a;
};
void main() {
A a;
}
delete:如果开发人员没有定义特殊成员函数,那么编译器在需要特殊成员的时候会隐式自动生成一个默认的特殊成员函数,而我有时候想禁止对对象的拷贝或者赋值,可以用delete进行修饰
class A {
public:
A() = default;
A(const A&) = delete;
A& operator = (const A&) = delete;
int a;
A(int i) { a = i; }
};
void main() {
A a1;
A a2 = a1;//错误,拷贝构造函数被禁用
A a3;
a3 = a1;//错误,拷贝赋值操作符被禁用
}
5.explicit
explicit专用于修饰构造函数,表示只能显式构造,不可以被隐式转换
不用explicit:
class A {
public:
A(int n) {
cout << "int" << endl;
}
A(const char* p) {
cout << "char" << endl;
}
};
void main() {
A a = 'f';;
}
执行结果会打印int,这里存在隐式类型转换,让char类型转成了int
修改后:
class A {
public:
explicit A(int n) {
cout << "int" << endl;
}
explicit A(const char* p) {
cout << "char" << endl;
}
};
void main() {
A a = 'f';// error,不可以隐式转换
}
6.委托构造函数
如果你的类有多个不同类型的构造函数,为了初始化成员肯定会有大量的重复代码,为了避免重复,常见的做法是把公共部分提取出来,放到一个init()函数里,然后构造函数再去调用,这种方法虽然可行,但效率和可读性较差,毕竟init()不是真正的构造函数
在C++11里,你就可以使用委托构造的新特性,一个构造函数直接调用另一个构造函数,把构造工作“委托”出去,既简单又高效
class DemoDelegating final {
private:
int a;
public:
DemoDelegating(int x):a(x){} //基本的构造函数
DemoDelegating(): //无参数的构造函数
DemoDelegating(0) //给出默认值,委托给第一个构造函数
{}
DemoDelegating(const string& s): //字符串参数构造函数
DemoDelegating(stoi(s)) //转换成整数,再委托给第一个构造函数
{}
};
7.成员初始化列表
成员初始化列表好处
更高效:少了一次调用默认构造函数的过程
有些场合必须要用初始化列表
1.常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
2.引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
3.没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化
8.lambda表达式
lambda表达式完整的声明格式如下
[capture list](params list)mutable exception->return type{function body}
各项具体含义如下
1.capture list:捕获外部变量列表
2.params list:形参列表
3.mutable指示符:是否可以修改捕获的变量
4.exception:异常设定
5.return type:返回类型
6.function body:函数体
此外,我们还可以省略其中的某些成分来声明不完整的lambda表达式,常见的有
[capture list](params list)->return type {function body}
[capture list](params list){function body}
[capture list]{fucntion body}
其中:
1.格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值
2.格式2省略了返回值类型,但是编译器可以根据以下规则推断出lambda表达式的类型
如果function body中存在return语句,则该lambda表达式的返回类型由return语句的返回类型确定
如果function body中没有return语句,则返回值为void类型
3.格式3中省略的参数列表,类时普通函数中的无参函数
捕获外部变量
1.值捕获
值捕获和参数传递中的值传递类似,被捕获的变量的值在lambda表达式创建时通过值拷贝的方式传入,因此随后对该变量的修改不会影响lambda表达式的值:
int a = 123;
auto f = [a] {cout << a << endl; };
a = 321;
f(); //输出123
这里需要注意,如果以传值方式捕获外部变量,则在lambda表达式函数中不能修改该外部变量的值
2.引用捕获
使用引用捕获一个外部变量,只需要在捕获列表变量前面加上一个引用说明符&
int a = 123;
auto f = [&a] {cout << a << endl; };
a = 321;
f(); //输出321
引用捕获变量实际上就是该引用所绑定的对象
3. 隐式捕获
我们可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获,隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量
int a = 123;
auto f = [&] { cout << a << endl; }; // 引用捕获
a = 321;
f(); // 输出:321
从工程的角度,大部分情况不推荐使用默认捕获符。更一般化的一条工程原则是:显式的代码比隐式的代码更容易维护。一般而言,按值捕获是比较安全的做法,按引用捕获时则需要更小心些,必须确保能被捕获的变量和lambda表达式的生命周期至少一样长,并且在下面需求之一时才能使用:
1.需要在lambda表达式中修改这个变量并让外部观察到
2.需要看到这个变量在外部被修改的结果
3.这个变量的复制代价比较高
9.智能指针
为什么使用智能指针:智能指针的作用时管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成了内存泄漏,使用智能指针可以很大程度上避免这个问题,因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源,所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
1.auto_ptr
采用所有权模式(C++98提出来的,C++11废弃)
auto_ptrp1(new string("hello")); auto_ptr p2; p2 = p1;
程序运行不会报错,p2剥夺了p1的所有权,当程序运行时访问p1会报错,所以auto_ptr的缺点是:存在潜在的内存崩溃问题
2.unique_ptr
unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象
unique_ptrp3 (new string ("hello")); unique_ptr p4; p4 = p3;//此时会报错!!
编译器认为p4=p3非法,避免了p3不再指向有效数据的问题,因此unique_ptr比auto_ptr更安全
3.shared_ptr
shared_ptr实现共享式拥有概念,多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放,它使用计数机制来表明资源被几个指针共享
4.weak_ptr
weak_ptr是一种不控制对象生命周期的智能指针,它指向一个shared_ptr管理对象。weak_ptr用来解决shared_ptr相互引用的死锁问题,它是对对象的一种弱引用,不会增加对象的引用计数,和shared+ptr之间可以相互转化,shared+ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr



