语言基础(2):C++标准简介

1、C++98标准

在C++98发布之时,已经具有类及派生类、共有和私有成员的区分、类的构造函数和析构函数、友元、内联函数、赋值运算符的重载,虚函数的概念、函数和运算符的重载、引用、常量(constant),类的保护成员、多重继承、对象的初始化与赋值的递归机制、抽象类、静态成员函数、const成员函数,模板(template)等C++基本特性,并且引入了尴尬的export关键字
更多关于C++ export关键字的参考

2、C++03标准

C++03标准主要是对C++98的勘误,唯一增加的新特性是值初始化(value initialization)
关于初始化的详细介绍可以参考:cppreference.com 上初始化章节


#3、C++11标准 目前看到很多人(包括公司)写代码用的是C++98规范,现在 C++ 标准C++17已经发布了,C++20也在规划了,C++11加入了一些常用新特性,这里将主要的特性汇总以下。 ##3.1 新语法 ###1. 自动类型推导auto auto的自动推导,用于从初始化表达式中推断出变量的数据类型。 ``` //C++98 int a = 10; string s = "abc"; float b = 10.0; vector c; vector > d; map m; m[1] = "aaa"; map::iterator it = m.begin();

//C++11
auto a1 = 10; //a1为int
auto s1 = "abc"; //s1为string
auto b1 = b;
auto c1 = c;
auto d1 = d;
auto e1 = 'a';
int* x = &a1;
auto d1 = x;
auto m1 = m.begin();
auto x=1,y=2; //ok
auto i=1.j=3.14; //compile error

double a2 = 3.144;
const auto a3 = a2; //const double
auto a4 = a2; //double
volatile int c2 = 3;
auto c3 = c2; //int

###2. 萃取类型decltype
decltype可以通过一个变量或表达式得到类型。

include

include

using namespace std;

int add(int a)
{
return ++a;
}

void fun(int a)
{
cout << "call function: [int]\n" << endl;
}

void fun(int a)
{
cout << "call function: [int
]\n" << endl;
}

int main()
{
//C++11
int aa = 10;
decltype(aa) bb = 11;
string ss = "hello intel";
decltype(ss) ss1 = "hello";
const vector vec(1);
decltype(vec[0]) cc = 1;
decltype(0) dd = vec[0]; //dd是int类型
decltype(add(1)) ee; //int
int a[5];
decltype(a) ff; //int[5]
//decltype(fun) gg; 无法通过编译,是个重载函数

return 0;

}

###3. nullptr
空指针标识符nullptr是一个表示空指针的标识,他不是一个整数,这是与我们常用的NULL宏的区别。NULL只是一个定义为常整数0的宏,而nullptr是C++11的一个关键字,一个內建的标识符。

include

include

using namespace std;

void fun(int a)
{
cout << "call function: [int]\n" << endl;
}

void fun(int a)
{
cout << "call function: [int
]\n" << endl;
}

int main()
{
//C++11
fun(NULL); //call function: [int]
fun(nullptr); //call function: [int*]

int* p = NULL;
fun(p);  //call function: [int*]

return 0;

}

###4. 区间迭代range for
C++98和C++11在使用语法上的差异如下:

include

include

using namespace std;

int main()
{
//C++98
vector vec(8, 1);
cout << "C++98 range for:" << endl;
for (vector::iterator it = vec.begin(); it != vec.end(); it++)
{
cout << *it << endl;
}

//C++11
cout << "C++11 range for:" << endl;
for (auto d : vec)
{
    cout << d << endl;
}

return 0;

}

值得指出的是,是否能够使用基于范围的for循环,必须依赖一些条件。首先,就是for循环迭代的范围是可确定的。对于类来说,如果该类有begin和end函数,那么for_each之间就是for循环迭代的范围。对于数组而言,就是数组的第一个和最后一个元素间的范围。其次,基于范围的for循环还要求迭代的对象实现+ + 和==等操作符。对于STL中的容器,如string、array、map等使用起来是不会有问题的。下面是C++11操作vector和数组的实践:

include

include

using namespace std;

int main()
{
vector vec(8, 1);

//C++11
cout << "C++11 value range for:" << endl;
/*d非引用,修改d不会影响vector里的值*/
for (auto d : vec)   //d中存储的是vec中的值
{
    d = 2;
}

for (auto d : vec)   
{
    cout << d << endl;
}

cout << "C++11 reference range for:" << endl;
/*当迭代变量d为引用时,vector里的值可以被修改*/
for (auto &d : vec)  
{
    d = 2;
}

for (auto d : vec)   
{
    cout << d << endl;
}

//数组for_each
char arr[] = {'a','b','c','d'};
for (auto &d : arr)
{
    d -= 32;
}
for (auto d : arr)
{
    cout << d << endl;
}

//遍历二维数组,注意迭代变量row必须为引用。如果你想用 range for 的方法,来遍历更高维的数组 (dim > 2),那么你只需要:除了最内层循环之外,其他所有外层循环都加入 '&' 即可。
int array2[5][5] = {0};
for (auto &row : array2)
    for (auto col : row)
        cout << col << endl;

return 0;

}

###5. 返回类型后置语法
先看下面这个例子,编译器在推导decltype(t1+t2)时表达式中t1和t2都未声明,所以编译失败。

include

include

using namespace std;

template<class T1, class T2>
decltype(t1 + t2) sum(T1 t1, T2 t2)
{
return t1 + t2;
}

int main()
{
auto total = sum(1, 2);
cout << total << endl;
return 0;
}

//所以C++11引入新语法,即把函数的返回值移至参数声明之后,复合符号->decltype(t1+t2)被称为追踪返回类型。而原本的函数返回值由auto占据。

include

include

using namespace std;

template<class T1, class T2>
auto sum(T1 t1, T2 t2) ->decltype(t1+t2)
{
return t1 + t2;
}

int main()
{
auto total = sum(1, 2);
cout << total << endl;
return 0;
}

###6. final和override

struct B
{
virtual void f1(int) const;
virtual void f2();
void f3();
};

struct D1 : public B
{
void f1(int) const override; //ok
void f2(int) override; //error,B中没有形如f2(int)的函数
void f3() override; //error,f3不是虚函数
void f4() override; //error,B中无f4函数
};

struct D2 : public B
{
void f1(int) const final; //不许后续的其他类覆盖
};

struct D3 :public D2
{
void f2();
void f1(int) const; //error,final函数不可覆盖
};
// final还可以用于防止继承的发生

class NoDerived final
{

};

class Bad :NoDerived //NoDerived不可做基类
{

};

class Base
{

};

class Last final :Base
{

};

class Bad2 :Last //Last不可做基类
{

};

###7. =default和=delete
对于 C++ 的类,如果程序员没有为其定义特殊成员函数,那么在需要用到某个特殊成员函数的时候,编译器会隐式的自动生成一个默认的特殊成员函数,比如拷贝构造函数,或者拷贝赋值操作符。

C++11允许我们使用=default来要求编译器生成一个默认构造函数,也允许我们使用=delete来告诉编译器不要为我们生成某个默认函数

class B
{
B() = default; //显示声明使用默认构造函数
B(const B&) = delete; //禁止使用类对象之间的拷贝
~B() = default; //显示声明使用默认析构函数
B& operator=(const B&) = delete; //禁止使用类对象之间的赋值
B(int a);
};

###8. lambda表达式
简单来说,Lambda函数也就是一个函数(匿名函数),它的语法定义如下:
[capture](parameters) mutable ->return-type{statement}
[=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量;
[&,a,this]表示以值传递的方式捕捉变量a和类的this指针,引用传递方式捕捉其它所有变量。

include

using namespace std;

int main()
{
auto f = {cout << "hello world!" << endl; };
f(); //hello world!

int a = 123;
auto f1 = [a] { cout << a << endl; };
f1();  //123

auto f2 = [&a] {cout << a << endl; };
a = 789;
f2();  //789

// 隐式捕获:让编译器根据函数体中的代码来推断需要捕获哪些变量
auto f3 = [=] {cout << a << endl; };
f3();  //789

auto f4 = [&] {cout << a << endl; };
a = 990;
f4();  //990

auto f5 = [](int a, int b)->int {return a + b; };
printf("%d\n", f5(1, 2));  //3

return 0;

}

// lambda表达式在C++下的应用,排序

include <stdio.h>

include

include

using namespace std;

void print(char arr[], int len)
{
for (int i = 0; i < len; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}

bool cmp(char a, char b)
{
if (a > b)
return true;
else
return false;
}

int main()
{
//c++98
char arr1[] = { 2,5,2,1,5,89,36,22,89 };
int len = sizeof(arr1) / sizeof(char);
sort(arr1, arr1 + len, cmp);
print(arr1, len);

//c++11
char arr2[] = { 2,5,2,1,5,89,36,22,89 };
int len2 = sizeof(arr2) / sizeof(char);
sort(arr2, arr2 + len2, [](char a, char b)->bool {return a > b; });
print(arr2, len2);
return 0;

}

###9. std::move
std::move是为性能而生,通过std::move,可以避免不必要的拷贝操作。std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。

include

include

include

include

int main()
{
std::string str = "Hello";
std::vectorstd::string v;
//调用常规的拷贝构造函数,新建字符数组,拷贝数据
v.push_back(str);
std::cout << "After copy, str is "" << str << ""\n"; //After move, str is "Hello"
//调用移动构造函数,掏空str,掏空后,最好不要使用str
v.push_back(std::move(str));
std::cout << "After move, str is "" << str << ""\n"; //After move, str is ""
std::cout << "The contents of the vector are "" << v[0]
<< "", "" << v[1] << ""\n"; //The contents of the vector are "Hello", "Hello"
}


##3.2 STL新内容
###1. std::array
使用 std::array保存在栈内存中,相比堆内存中的 std::vector,我们就能够灵活的访问这里面的元素,从而获得更高的性能;同时正式由于其堆内存存储的特性,有些时候我们还需要自己负责释放这些资源。

使用std::array能够让代码变得更加现代,且封装了一些操作函数,同时还能够友好的使用标准库中的容器算法等等,比如 std::sort。

std::array 会在编译时创建一个固定大小的数组,std::array 不能够被隐式的转换成指针,使用 std::array 很简单,只需指定其类型和大小即可:

include <stdio.h>

include

include

void foo(int* p)
{

}

int main()
{
std::array<int, 4> arr = {4,3,1,2};

foo(&arr[0]);  //OK
foo(arr.data());  //OK
//foo(arr);  //wrong
std::sort(arr.begin(), arr.end());  //排序

return 0;

}

###2. std::forward_list
std::forward_list 使用单向链表进行实现,提供了 O(1) 复杂度的元素插入,不支持快速随机访问(这也是链表的特点),也是标准库容器中唯一一个不提供 size() 方法的容器。当不需要双向迭代时,具有比 std::list 更高的空间利用率。

include <stdio.h>

include

include

include

include <forward_list>

int main()
{
std::forward_list list1 = { 1, 2, 3, 4 };

//从前面向foo1容器中添加数据,注意不支持push_back
list1.pop_front();  //删除链表第一个元素
list1.remove(3);   //删除链表值为3的节点
list1.push_front(2);
list1.push_front(1);
list1.push_front(14);
list1.push_front(17);

list1.sort();

for (auto &n : list1)
{
    if (n == 17)
        n = 19;
}

for (const auto &n : list1)
{
    std::cout << n << std::endl;  //1 2 2 4 14 19
}

return 0;

}

###3. std::unordered_map和std::unordered_set
无序容器中的元素是不进行排序的,内部通过 Hash 表实现,插入和搜索元素的平均复杂度为 O(constant),在不关心容器内部元素顺序时,能够获得显著的性能提升。

C++11 引入了两组无序容器:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multiset。

下面给出unordered_map和unordered_set的使用方法。

include <stdio.h>

include

include

include

include <unordered_map>

include <unordered_set>

void foo(int* p)
{

}

int main()
{
//unordered_map usage
std::unordered_map<std::string, int> um = { {"2",2},{"1",1},{"3",3} };

//遍历
for (const auto &n : um)
{
    std::cout << "key:" << n.first << "  value:" << n.second << std::endl;
}

std::cout << "value:" << um["1"] << std::endl;


//unordered_set usage
std::unordered_set<int> us = { 2,3,4,1};

//遍历
for (const auto &n : us)
{
    std::cout << "value:" << n << std::endl;
}

std::cout << "value:" << us.count(9) << std::endl; //判断一个数是否在集合内,1存在0不存在
std::cout << "value:" << *us.find(1) << std::endl;  //查找一个特定的数是否在集合内,找到就返回该数的迭代器位置

return 0;

}


##3.3 智能指针
###1. std::shared_ptr
shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。shared_ptr内部的引用计数是安全的,但是对象的读取需要加锁。

include <stdio.h>

include

include

int main()
{
//auto ptr = std::make_shared(10);
std::shared_ptr ptr(new int(10));
std::shared_ptr ptrC(ptr);

auto ptr2 = ptr;

{
    auto ptr3 = ptr2;
    std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  //4
    std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  //4
}

std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  //3
std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  //3

int *p = ptr.get(); //获取原始指针

std::cout << "pointer1.use_count() = " << ptr.use_count() << std::endl;  //3
std::cout << "pointer2.use_count() = " << ptr2.use_count() << std::endl;  //3

return 0;

}

###2. std::unique_ptr
std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全:

include <stdio.h>

include

include

int main()
{
std::unique_ptr ptr(new int(10));
//auto ptr2 = ptr; //非法

//虽说unique_ptr是不可复制的,但我们可以使用std::move将其独占权转移到其他的unique_ptr
auto ptr2(std::move(ptr));
std::cout << *ptr2 << std::endl;

return 0;

}

###3. std::weak_ptr
先观察下面的代码,如果我们在类father中使用的是shared_ptr son的话,father和son的实例并没有顺利回收,输出如下:

>father !
>son !

以上问题就是shared_ptr的环形引用问题。为了避免shared_ptr的环形引用问题,需要引入一个弱引用weak_ptr, weak_ptr是为了配合shared_ptr而引入的一种智能指针,弱引用不会引起引用计数增加,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况.

include

include

using namespace std;

class father;
class son;

class father {
public:
father() {
cout << "father !" << endl;
}
~father() {
cout << "~~~~~father !" << endl;
}
void setSon(shared_ptr s) {
son = s;
}
private:
//shared_ptr son;
weak_ptr son; // 用weak_ptr来替换
};

class son {
public:
son() {
cout << "son !" << endl;
}
~son() {
cout << "~~~~~~son !" << endl;
}
void setFather(shared_ptr f) {
father = f;
}
private:
shared_ptr father;
};

void test() {
shared_ptr f(new father());
shared_ptr s(new son());
f->setSon(s);
s->setFather(f);
}

int main()
{
test();
return 0;
}

输出:

>father !
>son !
>\~\~\~\~\~\~son !
>\~\~\~\~\~father !

##4、C++14&C++17
[C++14新特性](https://blog.csdn.net/zdy0_2004/article/details/40798997)
[C++17新特性](https://blog.csdn.net/fanyun_01/article/details/80471626)
posted @ 2017-08-12 20:04  ningKing  阅读(813)  评论(0编辑  收藏  举报