UPDATE 2017/03/10: 填了一部分
由于本人才疏学浅造成的错误希望各位在评论中心平气和地指正。
auto 关键字
在C语言中,这个关键字用于声明变量的生存期为自动,即将不在任何类、结构、枚举、联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量。这个关键字不怎么多写,因为函数内局部变量默认就是auto
的,与程序员预期相符合。C++98标准中auto
关键字与C语言中的相同;而自C++11以来,auto
的语义被修改,原来代表 “automatic” 的语义被废弃,该关键字转而用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
C++ auto
用法举例:
1 |
|
之所以上面返回类型需要使用auto
占位,是因为你的类型推导式用到了函数参数t
和u
,而编译器读到返回类型推导式decltype(t + u)
的时候t
和u
还没声明,也就用不了,形成了循环依赖。除了这种格式,还可以使用decltype(*(T*)0 + *(U*)0)
这样的格式,虽然更难看。
类型别名
类型别名用于避免又臭又长的变量声明(比如函数指针),以及保证可移植性(比如stdint.h
里的int32_t
保证是32位带符号整型)等等情况下。
C和C++均支持typedef
的类型别名,并且都不允许typedef
类型别名声明中出现static
. typedef
声明的作用域如同一个变量声明,在C++中多了类从而类中的typedef
声明也多了访问权限控制。
在C++中多出了模板,而typedef
不支持模板,定义的类型别名只能是模板实例化之后的类型。若要对模板进行类型别名声明新的模板,就要使用using
关键字。
using
的使用方式如下:
1 | // Simple using alias |
举个例子:
1 | template<class T> |
布尔类型
C在C99标准之前没有内置布尔类型,因此只能通过typedef int bool
的方式模拟;C99及以后在C语言中引入了布尔类型,其关键字是_Bool
. 如果想要像C++那样使用布尔类型的话,包含头文件stdbool.h
.
C++自带布尔类型,关键字是bool
.
数组
就定长数组(编译期可确定其长度)而言,C和C++毫无区别。而C99引入了变长数组(Variadic Length Array, VLA)之后,一切就变得不一样了。
就行为上来说,VLA相当于自动帮你使用了malloc
和free
, 只是开辟的空间在栈上罢了。同时对数组引入了新的语法(一般用在函数声明的参数列表里面),来描述新引入的VLA. 比如int[*]
代表这是一个int
的VLA, int[static 10]
代表这是一个至少有10个int
的VLA(该语法有利于优化).
C++中没有VLA, 如果一定要长度可变的数组那么使用std::vector
; 要封装原生数组使用C++11引入的std::array
.
泛型
这里的泛型是针对C11引入的_Generic
的直译。
在C11之前,我们无法在C语言中写一个针对所有数据类型都可用的算法(比如针对任何类型的数组都可用的排序函数),所能做的只有全部转换成void*
然后提供一个自定义的处理函数,通过这样拐弯抹角的方式来实现泛型。一个典型的例子就是stdlib.h
里的qsort
, 其原型定义如下:
1 | void qsort( void *ptr, size_t count, size_t size, |
在C11中提供了_Generic
的功能,它能根据传入参数的类型来决定对应的操作。如下就是一个_Generic
的例子,可以根据传入的参数到底是long double
还是float
来决定调用的函数到底是cbrtl
还是cbrtf
. 如果提供的类型无一满足,那么会使用default
条目定义的规则。
1 |
|
在C++中,语言自带的重载和模板特性就不知道比C高到哪里去了,虽然会有Name Mangling使ABI不稳定这种副作用。
struct
在C中,struct
只能内含数据类型,不能包有函数和static
成员。但C99之后也有一些C++没有的特性,比如Flexible array member(其实这个特性在进标准之前就有很多类似手法了),compound literal和designated initialization(这两个不光可用于struct
)。
1 | typedef struct test { |
这种缺乏导致C不能很方便地写出很“面向对象”的代码。成员函数和vtable可以手动添加struct
的函数指针成员来实现,假装自己有OOP的样子;而OOP中的继承特性就不太好写了。方法、成员的继承可以通过复制粘贴来实现,但基类可以指向子类,虚函数这些语义怎么办呢?
为了保持与C的兼容,C++让简单的struct
, class
, union
保持C风格,让它们能够与C传统的malloc
, memmove
等函数交互。
此外,在C中struct
, union
, enum
的类型名称和变量名、函数名等等是区别对待的,即使二者重名也是可以的。因此typedef struct tag tag
这样的语句很有必要,如果不写那么声明一个tag
结构体一定要使用struct tag var
而不是tag var
. 在C++中你可以认为是自动插入了这样的typedef
语句。
空指针
C语言中,任何类型的两个空指针值相等。宏定义NULL
的值具体取决于实现。C99中规定NULL
应该是从整数类型的0
显式或隐式地转换而来。
C++出于兼容C的考虑保留了NULL
, 其内容可能是:
- 整数类型的右值(rvalue)常量表达式,该值为0 (C++11之前)
- 整数类型的纯右值(prvalue)常量表达式,该值为0; 或是
std::nullptr_t
类型的纯右值 (C++11到C++14) - 值为0的整型字面值,或是或是
std::nullptr_t
类型的纯右值 (C++14起)
C++引入新的关键字nullptr
来代表空指针,来解决之前0
同时具有整数0和空指针语义的问题。例如如下的程序:
1 |
|
在上面的程序中,Fwd(g,NULL)
并不是如想象中一般地调用了g(int*)
,反而去寻找g(int)
的重载。使用nullptr
就不会出现这样的问题。
二者NULL
最原始的宏定义都在stddef.h
(或cstddef
)里。
类型转换
C支持隐式和显式的两种转换,隐式的由编译器自动完成,显式的只有(type)expression
这一种方式。
C++兼容C的类型转换方式,但它引入了对象机制后更加复杂。首先为了与C兼容保留了上面的转换方式,同时引入了等价的type(expression)
方式。除此之外,还存在static_cast
, const_cast
, dynamic_cast
, reinterpret_cast
这四种。从理论上来说,(type) expression
和type(expression)
都是以上几种XXXX_cast
的组合。在C++中,类型转换推荐使用XXXX_cast
的形式。
由于C++里面的构造函数和自定义转换函数,隐式转换可能会发生在各种意想不到的地方。关于类型转换的知识十分繁杂,具体详见C++隐式类型转换、C隐式类型转换。
指针和内存管理
这部分主要就是体现出C++强于C的地方了。涉及到动态内存管理,大家都只能向操作系统申请空间,到时候都得释放,但是C++能够玩出花来。利用类封装加上RAII机制,C++标准库实现了智能指针,一个是基于引用计数的shared_ptr
, 另一个是独享资源所有权的unique_ptr
, 利用这两样工具,基本能摆脱手动delete
的烦恼。
异常处理
C语言没有什么方便的异常处理机制,一般是在每个可能出异常的地方套上个if-else, 异常来临时exit
, 申请的资源通过在atexit
注册的函数来处理。或者使用setjmp
和longjmp
.
在C++中有现在满地都是的 try-catch 流,使用这个的时候需要时刻留心,保证代码的异常安全性。同时,在异常发生时使用原生指针管理的内存如果不在catch块处理那么内存就这么泄露了。
参考资料
auto部分:
类型别名部分:
布尔类型部分:
泛型部分:
空指针部分:
类型转换部分: