基于C++11标准的基本编程建议

1.C++标准历史沿革

C++11标准之前的C++被称为“ClassicC++”,即经典C++。C++11标准之后的C++被称为“ModernC++”即现代C++。

时间 名称 事件
1998 C++ 98 标准 1998年C++标准得到ISO和ANSI批准。以后每5年视实际需要更新一次。
2003 C++ 03 标准 2003年通过了C++标准第二版。
2011 C++ 11 标准 2011年发布了C++标准第四版C++ 11,取代现行的C++ 98 和 C++ 03。此次标准为 C++ 98发布13年来第一次重大修正。
2014 C++ 14 标准 2014年发布了C++标准第四版C++ 14,是 C++ 11的增量更新。

1.1查看你的编译器的C++标准

1
2
3
4
5
6
#include<iostream>
using namespace std;
int main(){
cout<<__cplusplus<<endl;
return 0;
}

对于实现了1998 C++标准或2003 C++标准的cplusplus的值为199711L;对于实现了2011 C++标准的__cplusplus的值为201103L。同理,实现了2014C++标准的值为201402L。

1.2更改项目的C++支持标准

​ 如果要设置项目支持C++17标准,可以【右键属性】----【C/C++】---- 【语言】---- C++语言标准,即可设置对应的标准,目前VS2017已经可以支持C++17标准.

2.基于C++11和geosoft.no的一些基本编程建议

2.1 std

​ std是C++中的一个命名空间,叫做标准命名空间。C++标准库中的函数或者对象都是在命名空间std中定义的,所以我们要使用标准函数库中的函数或对象都要使用std来限定。我们平常用的标准库中的cout,cin其实都来自std命名空间。using namespace std就是告诉编译器 ,下面的这些cout ,cin不是自定义的什么标识符,而是属于(尊贵皇家)名字空间std的std::cout和std::cin等等。

​ 但是不推荐在程序中使用”using namespace std;“,这是因为假如说我们自己定义了一个cout,并且头文件中包含了using namespace std。此时编译器就会报错,因为他不知道你到底想用哪一个cout。我们可以通过以下方式声明:

1
2
3
4
5
//事先声明
using std::cout;
using std::cin;
//或者使用
std::cout<<"Hello world"<<std::endl;

​ 在帮助同学解决问题的过程中,出现过一个与名字空间有关的问题。他定义了一个名字叫max的函数,在调用的时候频频报错,更改名字之后却可以正常编译。原来,他使用了“using namespace std;”之后,自定义的max函数与std名字空间里本来就有的max函数造成了冲突。

2.2 nullptr

​ 在使用VS2019编程时,每当要按照课本上所说的那样建立NULL空指针,NULL下面就会出现标红,这是为什么呢?新标准的C++应该使用什么来表示空指针呢?

2.2.1 0和NULL带来的二义性问题

​ C++03中,空指针使用“0”来表示。0既是一个常量整数,也是一个常量空指针。NULL本在C标准库中,在C++中变成了绝对的0,和0一样,在表示空指针时也具有二义性。

2.2.2 C++11中引入保留字“nullptr”作为空指针

​ C++标准化委员会希望“空指针”是一个确定的东西。nullptr是为了解决原来C++中NULL的二义性问题而引进的一种新的类型,由于NULL实际上代表的是0,它就可以强制转换成整型0;nullptr则不能与整型0之间进行转换,从而巧妙规避了编程中最受避讳的意义不明。

2.3 auto自动推断

2.3.1 auto功能的变化

​ 在C++03及之前的标准中,auto放在变量声明之前,声明变量的存储策略。但是这个关键字常省略不写。这点在我们的C++教科书上面有讲。
​ 在C++11中,auto关键字放在变量之前,作用是在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型。
例如:

1
2
3
int a = 10;
auto au_a = a;//自动类型推断,au_a为int类型
cout << typeid(au_a).name() << endl;

2.3.2 auto的使用限制

1.auto 变量必须在定义时初始化,这类似于const关键字

1
2
3
auto a1 = 10; //正确
auto b1; //错误,编译器无法推导b1的类型
b1 = 10;

2.定义在一个auto序列的变量必须始终推导成同一类型

1
2
auto a4 = 10, a5{20};  //正确
auto b4{10}, b5 = 20.0; //错误,没有推导为同一类型

3.如果初始化表达式是引用或const,则去除引用或const语义。

1
2
3
4
int a{10}; int &b = a;
auto c = b; //c的类型为int而非int&(去除引用)
const int a1{10};
auto b1 = a1; //b1的类型为int而非const int(去除const)

4.如果auto关键字带上&号,则不去除引用或const语意

1
2
3
4
int a = 10; int& b = a;
auto& d = b;//此时d的类型才为int&
const int a2 = 10;
auto& b2 = a2;//因为auto带上&,故不去除const,b2类型为const int

5.初始化表达式为数组时,auto关键字推导类型为指针。

1
2
3
int a3[3] = { 1, 2, 3 };
auto b3 = a3;
cout << typeid(b3).name() << endl; //输出int * (输出与编译器有关)

6.若表达式为数组且auto带上&,则推导类型为数组类型。

1
2
3
int a7[3] = { 1, 2, 3 };
auto& b7 = a7;
cout << typeid(b7).name() << endl; //输出int [3] (输出与编译器有关)

7.在C++14中,auto可以作为函数的返回值类型和参数类型。但auto作为函数返回值时,只能用于定义函数,不能用于声明函数。

2.4 列表初始化

2.4.1 C++11标准之前的初始化方法

1
2
3
4
5
int x = 0;		//C++98中的初始化
int y(2); //C++03中的初始化
char c('a');
int arr[] = { 1,2,3 };
char s[] = "Hello";

C++11标准仍然支持旧的初始化方法。

列表初始化是C++11的一个新特性,其中,“列表”是用花括号括起来的一(些)值。

2.4.2 列表初始化的两个分类

1.直接列表初始化

1
2
3
4
5
6
7
int y{ 1}; 

int array1[]{ 1,2,3 };

char s1[ 3 ] { 'o', 'k' };

char s3[]{ "Hello" };

2.拷贝列表初始化

1
2
3
4
5
6
7
8
9
int z = { 2 };

int array2[] = { 4,5,6 };

char s2[] = { 'y','e','s' };

char s4[] = { "World" };

char s5[] = "Aloha";

2.4.3 何时使用列表初始化

​ 列表初始化也被称为“统一初始化方法”,因为变量和数组可以使用同样的形式初始化,具有通用性。

​ 目前对于何时使用列表初始化仍然有一些争论。一种观点认为,要尽量使用列表初始化。这是因为列表初始化不允许“窄化”,即不允许丢失数据精度的隐式类型转换。

2.5 强制类型转换

​ 我们在编程时经常会遇到数据类型转换的问题,比如将浮点数转换为整数,或者将一个整数转换为字符串,这时就需要用到强制类型转换。通常,我们的老师会用C风格进行转换,那么C++风格的类型转换又有什么不同呢?

2.5.1 两种类型转换

隐式类型转换

​ 隐式类型转换是由编译器按照数据类型的转换规则自动转换,无需程序员干预。但它可能导致数据精度损失,甚至转换失败,所以我们不应该依赖隐式类型转换。

显式类型转换(即:强制类型转换)

​ 由程序员用明确的类型转换语法写出类型转换代码。

2.5.2 C和C++风格在类型转换方面的不同

C风格强制类型转换

语法:

1
2
(type) value
printf("%d", (int) 2.5);
C++风格强制类型转换

语法:

1
2
3
static_cast<type> value
out << static_cast<double>(1) / 2;
cout << 1 / 2;

最后,来看看编码规范:

45.Type conversions must always be done explicitly. Never rely on implicit type conversion.

45.类型转换必须显式声明。永远不要依赖隐式类型转换
例如:floatValue = static_cast(intValue); // NOT: floatValue = intValue;

2.6 主函数要有返回值

​ main函数的返回值用于说明程序的退出状态。如果返回0,则代表程序正常退出。返回其它数字的含义则由系统决定。通常,返回非零代表程序异常退出。
​ 不存在也不能使用void main(),并且新版本的编译器会报错,这会使程序的可移植性大大降低。

2.7 代码格式

​ 无论是花括号还是各种嵌套,为了程序的可读性,必要时要写注释,建议养成时刻关注自己代码排版的习惯。

​ 最后,笔者对C++11新特性的了解还比较浅显,欢迎各位指正错误、多多交流,也希望大家能在课外对cpp这门语言有更深刻的认识和思考,感谢阅读。

​ 注:部分资料来源于北京邮电大学教授崔毅东的《C++程序设计》课程资料。