1. C++11 新增_Pragma("once")来保证头文件只会被include一次,用以代替先前的两种写法,_Pragma是一个操作符,可以用在宏定义中,比预处理指令灵活。
1)#pragma once
2)#ifndef THIS_HEADER
#define THIS_HEADER
#endif
2. 变长参数的宏定义是指在宏定义中参数列表的最后一个参数为省略号,预定义宏__VA_ARGS__可以在宏定义的实现部分替换省略号所代表的字符串。
#define PR(...) printf(__VA_ARGS__)
6. C、C++混用头文件的典型做法如下,可以让如下头文件被#include到C文件中,也可以被#include 到C++文件中, extern "C"可以抑制C++对函数名、变量名等符号进行名称重整,因此编译出的C目标文件和C++目标文件中的变量名和函数名都一样的。
#ifdef __cplusplus
extern "C"{
#endif
.....
#ifdef __cplusplus
"}"
#endif
__cplusplus通常被定义为一个整数值,C++03标准中,其值是199711L, 在C++11中是201103L, 可以据此写出只支持C++11的代码,以下代码插入那些只能用C++11编译器编译的代码中(因为代码含C++11特性)
#if __cplusplus < 201103L
#error "should use c++ 11 implementation"
#endif
如下用法已经被c++废弃:
void excpt_func() throw (int , doulbe){}
void excpt_func() throw(){}
改为如下方式,可以没有常量表达式,默认是true:
void excpt_func() noexcept(常量表达式)
非常量的静态成员变量,C++98 和C++11 保持一致,都需要到类的头文件以外去定义,保证编译时非常量的类静态成员的定义最后只存在于一个目标文件中。
非静态的成员变量,C++98只能在初始化列表中进行初始化,C++ 11增加支持就地初始化(用{}方式,不能用()方式),若同时对一个非静态的成员变量进行就地初始化和初始化列表初始化,最后起作用的是初始化列表(即后起作用)。
10. C++98, sizeof 只能对实例的变量或者类的静态成员进行操作,不能对类的非静态成员进行操作,若要想达成对类的非静态成员的操作,可以用如下ugly方式, 0强转成对象的指针,并解析访问对应非静态成员变量。C++11之后,可以对类的非静态成员进行操作,无需此ugly方式。
#include <iostream>
using namespace std;
struct People{
public:
int hand;
static People* all;
};
int main(){
People p;
cout << sizeof(p.hand) << endl;
cout << sizeof(People::all) << endl;
cout << sizeof(((People*)(0))->hand) <<endl;
}
12. final,给程序员一种中途终止派生的能力,控制力更强。如果一开始就不想派生,最开始的基类设置为非虚函数就可以了,final一般用于中途终止派生。override, 明确告知这个函数是继承、重载的,避免以前写法的混淆。这两个关键字用于函数后面,才会产生此效果。虽然变量名可以用这两个,但是不建议。
13. C++98支持类模板的默认模板参数,不支持函数模板的默认模板参数,C++11可以支持函数模板的默认模板参数。类模板的默认模板参数需要从右到左依次,函数模板的默认模板参数没有此约束。默认模板参数通常是需要跟默认函数参数一起使用的。
14. 外部模板,extern 显示实例化一个模板函数(类),可以避免在多个编译单元都重复生成模板函数(类)的实例化代码,虽然在链接的时候会判断代码相同而做合并,但是会增加开销,有了外部模板,则可以大大缓解此问题。
15. 局部的类型、匿名的类型在C++98中不能作为模板类的实参,C++11已放开此限制。不过,匿名类型不能直接写在模板类的参数类型中,必须用独立的表达式语句。
C++11中,一个构造函数可以委派其他构造函数完成对象的构造,C++98中,需要将公共逻辑提取成一个公共私有函数,然后被多个构造函数调用。
委派构造函数:调用目标构造函数完成构造的构造函数;
目标构造函数:基准的构造函数,被委派构造函数调用。构造函数不能既是委派构造函数,又含有初始化列表。
目标构造函数抛出异常,委派构造函数可以catch住(委派构造函数的函数体不会再执行),但是最终程序还是会core dump的,见如下小程序演示:
class DCExcept{
public:
DCExcept(double d)
try : DCExcept(1, d){
cout << "run the body." << endl;
}catch(...){
cout << "caught exception."<<endl;
}
private:
DCExcept(int i, double d){
cout << "going to throw" << endl;
throw 0;
}
int type;
double date;
};
going to throw
caught exception.
libc++abi.dylib: terminating with uncaught exception of type int
Abort trap: 6
18. 左值,右值(右值又分将亡值、纯右值)
将亡值是C++11新增的跟右值引用相关的表达式,通常是要被移动的对象。右值引用,对右值进行引用的类型,因为右值通常没有名字,所以只能用引用的方式找到他的存在,举例,T&& a = ReturnRvalue(); ReturnRvalue()必须返回一个右值,不能返回左值。右值引用和移动语义紧密联系,可以延续右值的生命周期,并且可以对右值进行修改;虽然可以定义常量右值引用,但是意义不大。
一般来说,左值引用不能接受一个右值,不过常量的左值引用是能接受一个右值的,比如,函数的引用传递,const T&, 可以减少临时对象的拷贝。
is_rvalue_reference, is_lvalue_reference, is_reference 可以判断是左值引用、右值引用、引用。举例,is_revalue_reference<string &&>::value , value取值true。
std::move , 可以强制将一个左值转化为右值,以便可以通过右值引用使用该值,从而实现移动语义。对堆内存、文件句柄等资源成员使用move,如果成员支持移动构造,则可以实现移动语义,若成员没有移动构造函数,参数类型为常量左值引用的构造函数版本也会轻松的实现拷贝构造;
#include <iostream>
#include <utility>
#include <vector>
#include <string>
using namespace std;
int main()
{
string str = "Hello World!";
vector<string> v;
// 使用 push_back(const T&) , 复制str的内容,不是移动
v.push_back(str);
// 打印如下:After copy, str is "Hello World!"
cout << "After copy, str is \"" << str << "\"\n";
// 使用右值引用 push_back(T&&) ,移动str的内容,不是复制,
v.push_back(move(str));
// 打印如下:After move, str is ""
cout << "After move, str is \"" << str << "\"\n";
// 打印如下(包含第一次复制进来的,总共两个):The contents of the vector are "Hello World!", "Hello World!"
cout << "The contents of the vector are \"" << v[0] << "\", \"" << v[1] << "\"\n";
}
19. 借助右值引用实现完美转发逻辑
template <typename T, typename U>
void PerfectForward(T &&t, U& Func){
cout << t << "\tforwarded.." <<endl;
Func(forward<T>(t));
}
20. 列表初始化,C++11新增了列表初始化方式,方便了代码编写。声明一个以initializer_list<T> 模板类为参数的构造函数,自定义的类可以使用列表初始化方式。
int b[]{2,4,5};
vector<int> c{1,3,5};
map<int, float> d = {{1,1.0f}, {2, 2.0f}, {5, 3.2f}};
int a = {3+4};
int a{3+4};
int a(3+4);
vector<T>::vector<T>(initializer_list<T> elements)
{
......
}
std::vector<int> vec= {1,2,3,4,5};
C++98标准:不允许非POD类型,不允许拥有静态或者引用类型的成员。
C++11标准:任何非引用类型都可以成为联合体的数据成员。
22. 字面量操作符函数
下面函数会解析以_C为后缀(后缀可以自己定义)的字符串,并返回一个T的临时变量, 括号中参数列表取决于实际情况会有不同,operator "" 与用户自定义后缀之间必须有空格,后缀建议以下划线开始,否则会编译器告警。
T operator "" _C (const char* col, size_t n)
若字面量为整型数,字面量操作符函数只可接受unsigned long long 或者 const char* 为其参数, 当unsigned long long 无法容纳该字面量的时候,编译器会自动将字面量转换为以\0为结尾符的字符串,并调用const char* 为参数的版本进行处理。
若字面量为浮点类型,则字面量操作符函数只可接受long double 或者const char* 为参数,const char*版本的使用规则同第一点(过长则使用const char*版本)。
若字面量为字符串,则字面量操作符函数只可接受const char* ,size_t为参数(已知长度的字符串)。
若字面量为字符,则字面量操作函数只可接受一个char为参数。
long double operator "" _w(long double);
std::string operator "" _w(const char16_t*, size_t);
unsigned operator "" _w(const char*);
int main() {
1.2_w; // 调用运算符 "" _w(1.2L)
u"one"_w; // 调用运算符 "" _w(u"one", 3)
12_w; // 调用运算符 "" _w("12")
"two"_w; // 错误:无适用的字面量运算符
}
23. 扩展了using的使用,不只用来声明命名空间了,using namespace xxx;
using uint = unsigned int ;
template<typename T>
using MapString = map<T, char*>;
MapString<int> numberedString;
C++98中用于自动存储期的局部变量,此特性几乎无人使用;C++11改变了语义,作为新的类型指示符,用于自动推导,特别是命名空间、模板等导致类型很长的时候,非常方便。对于指针类型,声明为auto* 或者auto 是一样的,对于引用类型,必须使用auto & 。
int x;
int * y = &x;
double foo(){
return 2.0;
}
auto*a = &x;
auto c = y;
auto * d = y;
//auto * e = &foo();---这个有问题,指针不能指向临时变量
auto & b = x;
const auto &f = foo();
auto &&f = foo();
auto g = x;
auto & h = x;
25. decltype,以一个普通的表达式为参数返回该表达式的类型,在编译期进行。
template <typename T1, typename T2>
auto Mul(const T1& t1, const T2& t2) -> decltype(t1 * t2){
return t1 * t2;
}
26. 基于范围的for循环,对于知道范围的数组和stl容器,C++11提供了for迭代访问能力,结合auto,可简化代码。需要满足两个条件:1)迭代的对象实现++和==操作;2)知道范围
int main(){
vector<int> v = {1,2,3,4,5};
for(auto i = v.begin(); i != v.end();++i)
{
cout << *i << '\t';
}
cout << endl;
for(auto &e:v){
e *= 2;
}
for(auto e:v)
{
cout << e << '\t';
}
cout << endl;
}
针对C++98中枚举类型的一些缺点,C++11做了如下扩展:
在C++98中,枚举值没有类或者命名空间的约束,两个枚举类型的枚举值若是一样,则会产生冲突, 在C++11中,采用类名来约束,从而避免了冲突。
在C++98中,枚举值和整型可以隐式转换,在C++11中,必须显示转换。
在C++98中,底层存储大小不确定,在C++11中,可以指定底层存储类型。
// C++11中的用法
enum Type:char{General,Light, Medium, Heavy};
enum class Type:char{General, Light, Medium, Heavy};
C++98中,智能指针采用auto_ptr ,拷贝的时候返回的是一个左值,且不能调用delete[], C++11 已经废弃,改用如下三种智能指针:
unique_ptr:拥有管理内存的所有权,没有拷贝构造函数,只有移动构造函数,不能多个unique_ptr对象共享一段内存,可以自定义delete函数,从而支持delete [] 。
share_ptr:通过计数方式多个share_ptr可以共享一段内存,当计数为0的时候,所管理内存会被删除,可以自定义delete函数,从而支持delete[] 。
weak_ptr:观察shared_ptr管理的内存对象 ,只观察但不拥有。成员函数lock返回shared_ptr对象,若对应内存已经删除,则shared_ptr对象==nullptr,weak_ptr对象可以拷贝构造,拷贝构造出来的对象和原对象观察的是同一段内存。成员函数reset可以解除对内存的观察,注意,是解除观察,并不会删除对应内存对象。可以避免因shared_ptr的循环引用而引起的内存泄露,见如下对比使用方式:
#include <memory>
#include <string>
#include <iostream>
using namespace std;
class B;
class A
{
public:
shared_ptr<class B> m_spB;
};
class B
{
public:
shared_ptr<class A> m_spA;
};
class D;
class C
{
public:
weak_ptr<class D> m_wpD;
};
class D
{
public:
weak_ptr<class C> m_wpC;
};
void test_loop_ref()
{
weak_ptr<class A> wp1;
{
auto pA = make_shared<class A>();
auto pB = make_shared<class B>();
pA->m_spB = pB;
pB->m_spA = pA;
wp1 = pA;
}
cout << "wp1 reference number: " << wp1.use_count() << "\n";// 存在内存泄露,打印如下,wp1 reference number:1
weak_ptr<class C> wp2;
{
auto pC = make_shared<class C>();
auto pD = make_shared<class D>();
pC->m_wpD = pD;
pD->m_wpC = pC;
wp2 = pC;
}
cout << "wp2 reference number: " << wp2.use_count() << "\n";// 没有内存泄露,打印如下,wp2 reference number:0
}
int main()
{
//std::weak_ptr 用来避免 std::shared_ptr 的循环引用
test_loop_ref();
return 0;
}
29. 最小垃圾回收支持
安全派生指针定义:
在解引用基础上的引用,例如&*p
定义明确的指针操作,例如,p+1
定义明确的指针转换,例如:static_cast<void *>(p)
C++ 11最小垃圾回收支持,基于安全派生指针这个概念,enum class pointer_safety { relaxed, preferred, strict };
relaxed:完全不支持垃圾回收,和C++98/03一样。
preferred:垃圾回收器用于一些辅助功能,例如内存泄露检测或检测对象是否被一个错误的指针解引用。
目前还没有编译支持最小垃圾回收。C++11规则通过如下两个api来包裹一段内存,通知垃圾回收器不得回收:
void declare_reachable(void* p);
template<class T> T* undeclare_reachable(T *p) noexcept;
下面语句可以让垃圾回收器不关心从p开始的连续n的内存:
void declare_no_pointers(char*p ,size_t n)noexcept;
void undeclare_no_pointers(char*p ,size_t n) noexcept;
30. 常量表达式
编译时常量:constexpr
31. 多线程编程
C++11之前,在C/C++中程序中使用线程,主要使用POSIX线程(pthread),POSIX线程是POSIX标准中关于线程的部分,程序员可以通过pthread线程的api完成线程的创建、数据的共享、同步等功能。C++11引入了多线程的支持,使得C/C++语言在进行线程编程时,不必依赖第三方库和标准。
C++98 实现的多线程
#include <pthread.h>
#include <iostream>
using namespace std;
static long long total = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
void* func(void*){
long long i;
for(i = 0; i < 100000000L;++i){
pthread_mutex_lock(&m);
total += i;
pthread_mutex_unlock(&m);
}
return NULL;
}
int main(){
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, &func, NULL)){
throw;
}
if(pthread_create(&thread2, NULL, &func, NULL)){
throw;
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
cout << total << endl;
return 0;
}
C++11 实现的多线程
#include <atomic>
#include <thread>
#include <iostream>
using namespace std;
atomic_llong total{0};
void func(int ){
for (long long i = 0; i < 100000000LL;++i){
total += i;
}
}
int main(){
thread t1(func, 0);
thread t2(func ,0);
t1.join();
t2.join();
cout << total << endl;
return 0;
}
线程局部存储变量:拥有线程生命周期及线程可见性的变量。