C++Annoatation 02: More extensions to C , Some features of C++11
More extensions to C , Some features of C++11
对于稍微有些C++的同学,大概都知道C++和C的一些主要区别,大的区别就是一个是面向对象,一个是面向过程,这就涉及到对象; 同时,还有命名空间的引入,防止变量名冲突; 还有struct支持成员函数等。 我就继续注释一些更需要注意的一些不同,一些是C++11中的东西。
引用
可以通过引用修改它引的对象,但是不能改变它本身。这么听来,就像是常量指针, 另外,引用也让代码更加自然,不像c里边会出现一堆的&来表示传址调用。
int a=1;
int &r=a;
int* const rr = a; // int*const 和 int&是同一个东西,引用只是一个语法糖。
int &r=a;
int* const rr = a; // int*const 和 int&是同一个东西,引用只是一个语法糖。
使用引用时,有几点需要记住:
引用定义时必须初始化! 其实看上面的程序例子就知道, const的东西都必须初始化
不要在函数中返回局部变量的引用
函数中是否使用引用的场合
引用,可以当做函数参数传入,这样就可能会修改所引用对象。指针也能达到这样的目的,这样多多少少会造成一些困惑,我们可以依从一些原则:
若形参是内建类型(int double char等),且我们不需要改变实参值,直接传值调用
若形参时内建类型,且我们需要修改它,那就通过指针*调用
若形参类型是类,结构体等,我们不需要改变它的成员变量,采用 const &
调用
若形参类型是类等,需要改变,采用引用&调用
使用引用调用,能够避免构造函数的调用,节省开支。
另外,需要实现链式操作的,返回值选择引用类型,也能避免构造函数的调用。经典例子就是 “cout”
右值引用 (对C++11不是很熟悉,会有一些理解上的错误,请指正)
C++11中多了个右值引用。 主要是因为临时变量(右值)不能很好的同 const&
很好的区分开来,因此C++11多了个右值引用&&。
举例说明下:
int intVa()
{
return 5;
}
int& a = intVal(); // 错误! 引用临时变量
int const& a = intVal(); // 正确,引用不变临时变量
int* a = &intVal(); // 错误! intVal()不是左值
{
return 5;
}
int& a = intVal(); // 错误! 引用临时变量
int const& a = intVal(); // 正确,引用不变临时变量
int* a = &intVal(); // 错误! intVal()不是左值
我们继续看:
void receive(int& value){++value; cout <<“L-value parameter\n”;} // 左值引用版本
void receive(int&& value){++value; cout << "R-value parameter\n";} // 右值引用版本
void receive(int const& value) {++value; cout << "L-value const parameter\n";} // ++value不能有,否则编译错误。这算是右值引用和const&的区别。
void receive(int&& value){++value; cout << "R-value parameter\n";} // 右值引用版本
void receive(int const& value) {++value; cout << "L-value const parameter\n";} // ++value不能有,否则编译错误。这算是右值引用和const&的区别。
如此调用:
int main()
{
receive(18); // R-value 19
int v = 5;
int const& cri = v;
receive(cri); // L-value const
receive(v); // L-value 6
receive(intVal()); // R-value 6
}
{
receive(18); // R-value 19
int v = 5;
int const& cri = v;
receive(cri); // L-value const
receive(v); // L-value 6
receive(intVal()); // R-value 6
}
若我们把 void receive(int& value)删除,则receive(v) 返回 L-value
const parameter。 函数重载的右值引用版本,仅在匿名变量传入时激发。
另外,右值引用版本函数中,可以对value进行操作,可以看出,右值引用 用于获取 匿名临时变量 的 操纵权,而 int
const& 版本的却不行。
注意下:
若引入另外个重载版本 void(int value), 那么将发生错误,因为该例中,匿名变量也可以解析成 int
型,导致重载冲突。
另外,右值引用的进阶应用是 是 移动语义 move semantics 和 完美转发 perfect
forwarding 的基础。移动语义我暂时还不懂啥,以后再讲述。
强类型-枚举类
C++中,枚举类型其实就是int,不同枚举类型可以通过static_cast<int>
来进行比较操作;另外,不同枚举类型的内部值不能相同,因为C++中枚举类型的作用范围不被枚举名所限,而是受作用域限制。
C++11中,引入了 enum class ,来解决上述问题。
enum class CharEnum: unsigned char //
默认是int,可以通过这个“:”符号改变
{
NOT_OK, // 默认 0
OK // 自增1
};
{
NOT_OK, // 默认 0
OK // 自增1
};
使用时,要加入枚举类名与作用域符,即 CharEnum::OK.
前向声明如下
enum Enum1; // Illegal: no size
available
enum Enum2: unsigned int; // Legal in C++11: explicitly declared type
enum class Enum3; // Legal in C++11: default int type is used
enum class Enum4: char; // Legal in C++11: explicitly declared type
enum Enum2: unsigned int; // Legal in C++11: explicitly declared type
enum class Enum3; // Legal in C++11: default int type is used
enum class Enum4: char; // Legal in C++11: explicitly declared type
初始化列表
c语言中,可以用大括号包含一个初始化列表来初始化数组、结构体。C++11把这个概念继续扩展了,引入了
initializer_list<Type>
这个模本类,可以扩展初始化类,同时也能对初始化列表进行个性化操作。 使用前,要包含头文件 <initializer_list>
。同时,可以将初始化列表当做函数形参传入。
void values(initializer_list<int> iniValues)
{
cout << "Initializer list having " << iniValues.size() << "values\n";
for
(
initializer_list<int>::const_iterator begin = iniValues.begin();
begin != iniValues.end();
++begin
)
cout << "Value: " << *begin << '\n';
}
{
cout << "Initializer list having " << iniValues.size() << "values\n";
for
(
initializer_list<int>::const_iterator begin = iniValues.begin();
begin != iniValues.end();
++begin
)
cout << "Value: " << *begin << '\n';
}
这个知识点Mark下,以后继续深入了解,记得在 boost 中好像也有类似的东西。
auto 和 decltype 这个我觉得,最好了
由于C++的声明可能会非常复杂难懂,C++11将曾经的auto关键字改造,使其能够自动解析类型,不错,基于boost的改进。
auto 和 decltype
功能异常强大,不过个人认为,还是要把模板知识搞透了再深入使用这个关键字比较好,基础神马的,最重要的了。
int (*fun())[10], 表示
fun是个函数,返回一个指向int数组[10]的指针。这样不好理解,用上auto后,一切这么自然
auto func() -> int(*)[10];
另外,比如 vector<int>::const_iterator ci = v.begin();
可以改写成 auto ci = v.begin(); 例子很多,相信auto的使用,会让C++更加受欢迎。
类型定义和 ‘using’ 声明
比如 unsigned long int compute(double, double),
这个函数指针的声明,使用typedef可以变成这样:
typedef unsigned long int(*pfun)(double, double);
但是,我们声明时 pfun f;
时,掩盖了Pfun实际上是一个指针的事实,必须看typedef定义才知道。关于这点,我很诟病,导致我函数指针一直没学好。C++11中将这点进行了升华,采用using
关键字:
using pfun =
unsigned long int(double, double);
或者和auto配合:
using pfun = auto (double,double) -> unsigned long int;
或者和auto配合:
using pfun = auto (double,double) -> unsigned long int;
for循环的范围操作
传统C/C++ 的for采用标准的
for(init;cong;next)语句,这对遍历的使用,不那么的便捷,很多语言都提供了范围操作符,在STL的算法库也包含了for_each方法,但是还是不够便捷。C++11中引入了for的范围操作版本:
int array[100];
for (auto &element:array){...}
这里,推荐使用auto,避免思想负担在解析类型上。 同时,element是变量名,表示array中每次遍历的元素。
引用操作符非常重要,若是要修改元素或array是类类型,则用引用; 若是类类型,但不改变,用const&;
普通的内建类型且不改变,可以不用&。
有了这个范围操作,C++算是一大进步呀。
原始字符串
传统C/C++使用""包含字符串,用\来做逃脱而字符。这样,我们会在代码中看到一堆的\\\\\\
实在很影响阅读。现在很多语言都支持正则表达式了,而正则中最多也正是\\这样的符号,让C++来使用,my god!
。我觉得C++可能借鉴了python的R字符串,和perl的定界符规则,引出自己的原始字符串,在原始字符串中,不存在逃脱语义。写法如下:
R"(string)" 写法一
R"delimiter(string)delimiter" 写法二
更细致的阐述,打算放在正则表达式时讨论。
新增类型说明符
ob0101:
表示二进制的5
F : 说明是一个浮点型常量 3F
L : 前缀使用说明字符串中字符是wchar_t, 后缀使用说明是一个 long
p :十六进制数*(2^p), 即左移p。
增加对 Unicode 的支持
字符串:
char utf_8[] = u8"This is UTF-8 encoded.";
char16_t utf16[] = u"This is UTF-16 encoded.";
char32_t utf32[] = U"This is UTF-32 encoded.";
char utf_8[] = u8"This is UTF-8 encoded.";
char16_t utf16[] = u"This is UTF-16 encoded.";
char32_t utf32[] = U"This is UTF-32 encoded.";
对unicode常量,使用\u逃脱,加上一个十六进制量
char utf_8[] = u8"\u2018";
char16_t utf16[] = u"\u2018";
char32_t utf32[] = U"\u2018";
char utf_8[] = u8"\u2018";
char16_t utf16[] = u"\u2018";
char32_t utf32[] = U"\u2018";
感慨:C++复杂太多了,多了太多东西了,每个知识点都是一大章的东西,看来,路漫漫呀!
路漫漫其修远兮,吾将上下而求索