C++11
# 变量类型推导
# auto类型推导
在定义变量时,必须先给出变量的实际类型,编译器才允许定义,但有些情况下可能不知道需要实际类型怎
么给,或者类型写起来特别复杂,使用auto
来类型推导会很方便。
# decltype类型推导
auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。但有 时候可能需要根据表达式运行完成之后结果的类型进行推导,因为编译期间,代码不会运行,此时auto也就 无能为力。所以需要decltype
int a = 100;
int b = 200;
decltype(a + b) c = 300;
cout << typeid(c).name() << endl; // i
2
3
4
5
# 默认成员函数控制
在C++中对于空类编译器会生成一些默认的成员函数,比如:构造函数、拷贝构造函数、运算符重载、析构 函数和&和const&的重载、移动构造、移动拷贝构造等函数。如果在类中显式定义了,编译器将不会重新生 成默认版本。有时候这样的规则可能被忘记,最常见的是声明了带参数的构造函数,必要时则需要定义不带 参数的版本以实例化无参的对象。而且有时编译器会生成,有时又不生成,容易造成混乱,于是C++11让程 序员可以控制是否需要编译器生成。
# 显式缺省函数
在C++11中,可以在默认函数定义或者声明时加上=default
,从而显式的指示编译器生成该函数的默认版
本,用=default
修饰的函数称为显式缺省函数。
# 删除默认函数
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且不给定义,这样只要其他
人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete
即可,该语法指示编译器不生成对
应函数的默认版本,称=delete
修饰的函数为删除函数。
# 可变参数模板
语法:
template<class ...Args>
返回类型 函数名(Args... args) {
//函数体
}
2
3
4
例子:
template<class ...Args>
void ShowList(Args... args) {}
2
template<typename T>
void print(T item) { // 基础情况
cout << item << " ";
}
template<typename T, typename... Args>
void print(T first, Args... args) { // 递归情况
print(first);
print(args...);
}
int main() {
print(1, 2.5, "three", 'a', true); // 1 2.5 three a 1
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 右值引用和移动语义
左值引用主要引用左值,左值通常可以修改,比如变量 右值引用主要引用右值,右值通常不可以修改,比如常量、表达式、函数的返回值及临时变量等,C++11还将右值区分为纯右值和将亡值。
- 纯右值:比如:a+b, 100
- 将亡值:比如:表达式的中间结果、函数按照值的方式进行返回
左值引用不能直接引用右值,但是const + 左值引用
可以引用右值
右值引用不能直接引用左值,但是可以引用move
后的左值
// 左值引用
int a = 100;
int& b = a;
// 右值引用
int&& c = 100;
const int& d = 100;
int&& e = move(a);
return 0;
2
3
4
5
6
7
8
9
在operator+中:strRet在按照值返回时,必须创建一个临时对象,临时对象创建好之后,strRet就被销毁 了,最后使用返回的临时对象构造s3,s3构造好之后,临时对象就被销毁了。仔细观察会发现:strRet、临 时对象、s3每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完 全相同的对象,对于空间是一种浪费,程序的效率也会降低,而且临时对象确实作用不是很大,那能否对该 种情况进行优化呢?
//...
# lambda表达式
class Goods {
public:
string name;
double price;
Goods(const string _name, const double _price) : name(_name), price(_price) {}
};
class priceSortLess {
public:
bool operator()(const Goods& g1, const Goods& g2) const {
return g1.price > g2.price;
}
};
int main() {
vector<Goods> goods = {
Goods("apple", 12.3),
Goods("pear", 10.7),
Goods("banana", 10.1)
};
sort(goods.begin(),goods.end(),priceSortLess());
for(auto i : goods) {
cout << i.name << " + " << i.price << endl;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法, 都要重新去 写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了 极大的不便。因此,在C11语法中出现了Lambda表达式。
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
- [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来 的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
- [var]:表示值传递方式捕捉变量var。
- [=]:表示值传递方式捕获所有父作用域中的变量(成员函数包括this指针)。
- [&var]:表示引用传递捕捉变量var。
- [&]:表示引用传递捕捉所有父作用域中的变量(成员函数包括this指针)。
- [this]:表示值传递方式捕捉当前的this指针。
- (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起 省略
- mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。使用该修 饰符时,参数列表不可省略(即使参数为空)。
- ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分 可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
- {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。
注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。
在上面的例子中,可以简化书写为下面的代码:
sort(goods.begin(), goods.end(),
[](const Goods &g1, const Goods &g2)-> bool {
return g1.price > g2.price;
});
2
3
4
# 类型转换
C语言支持隐式转换和显式转换
- 隐式类型转换:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
- 显式类型转换:需要用户自己处理,以
(指定类型)变量
的方式进行类型转换。
int main() {
//隐式类型转换
int i = 1;
double d = i;
cout << i << endl;
cout << d << endl;
//显式类型转换
int* p = &i;
int address = (int)p;
cout << p << endl;
cout << address << endl;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符,分别是static_cast
、reinterpret_cast
、const_cast
和dynamic_cast
。
# static_cast
static_cast用于相近类型之间的转换,编译器隐式
执行的任何类型转换都可用static_cast,但它不能用于两个不相关类型之间转换。
int i = 100;
auto d = static_cast<double>(i);
2
# reinterpret_cat
reinterpret_cast用于两个不相关类型之间的转换。
int i = 100;
int* p = nullptr;
p = reinterpret_cast<int*>(i);
2
3
# const_cast
const_cast用于删除变量的const属性,转换后就可以对const变量的值进行修改。
const int i = 100;
auto ptr = const_cast<int*>(&i);
*ptr = 1000;
cout << *ptr << endl; // 1000
cout << i << endl; // 100
2
3
4
5
- 由于编译器认为const修饰的变量是不会被修改的,因此会将const修饰的变量存放到寄存器当中,当需要读取const变量时就会直接从寄存器中进行读取,而我们修改的实际上是内存中的a的值,因此最终打印出a的值是未修改之前的值。
- 如果不想让编译器将const变量优化到寄存器当中,可以用
volatile关键字
对const变量进行修饰,这时当要读取这个const变量时编译器就会从内存中进行读取,即保持了该变量在内存中的可见性。
# dynamic_cast
dynamic_cast用于将父类的指针(或引用)转换成子类的指针(或引用)。
- **向上转型:**子类的指针(或引用)→ 父类的指针(或引用)
- **向下转型:**父类的指针(或引用)→ 子类的指针(或引用)
向下转型分为两种情况:
- 如果父类的指针(或引用)指向的是一个父类对象,那么将其转换为子类的指针(或引用)是不安全的,因为转换后可能会访问到子类的资源,而这个资源是父类对象所没有的。
- 如果父类的指针(或引用)指向的是一个子类对象,那么将其转换为子类的指针(或引用)则是安全的
class A {
public:
virtual void f() {}
};
class B : public A
{};
void func(A* pa) {
B* pb1 = (B*)pa; //不安全
B* pb2 = dynamic_cast<B*>(pa); //安全
cout << "pb1: " << pb1 << endl;
cout << "pb2: " << pb2 << endl;
}
int main() {
A a;
B b;
func(&a);
func(&b);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
注意:
dynamic_cast只能用于含有虚函数的类,因为运行时类型检查需要运行时的类型信息,而这个信息是存储在虚函数表中的,只有定义了虚函数的类才有虚函数表。