C++由来已久,也做了好几年了,但是还是有好多功能没用到过,并且随着 c++11的推出,增加了不少新功能,这里开始做笔记。
强枚举类型
普通的 enum 类型是弱类型的,默认是整形,并且可以发生隐式类型转换,c++ 也有提供强类型的枚举类型:
1 | enum class MyEnum // class 表示强枚举类型 |
默认情况下,枚举值的基本类型是整型,可以采用以下方式加以改变:
1 | enum class MyEnumLong: unsigned long |
std::array
常用的如int arr[]
这种形式是来自于 C 的数组形式,C++ 有一种==大小固定==的特殊容器 std::array,来自于
- 总是知道自身的大小
- 不会自动转换为指针,从而避免某些类型的 bug。
- 具有迭代器,可以方便的遍历元素。
示例代码:
1 | array<int, 3> arr = {9, 8, 7}; |
基于区间的 for 循环
这种循环允许方便地迭代容器中的元素,这种循环类型可以用于 C 风格的数组、初始化列表,也可以用于具有返回迭代器的 begin() 和 end()函数的类型。
1 | std::array<int, 4> arr = {1, 2, 3, 4}; |
替代的函数语法
自 C++11 以来,通过拖尾返回类型(trailing return type)支持一种替代的函数语法,这种语法在指定模板函数的返回类型时非常有用。
1 | auto func(int i) -> int |
关键字 auto 和 decltype
关键字 decltype 把表达式作为实参,计算出该表达式的类型。例如:
1 | int x = 123; |
这里要强调的是,auto 推断表达式的类型,会去除引用限定符和 const 限定符,而 decltype 没有去除这些限定符。例如:
1 | const string message = "Test"; |
智能指针
为了避免常见的内存问题,应使用智能指针代替通常的 C 样式指针。C++ 中有三种智能指针:std::unique_str
、std::shared_ptr
和 std::weak_ptr
,他们都在头文件<memory>
中定义。
unique_ptr
unique_ptr
类似于普通指针,但在 unique_ptr
超出作用于或被删除时,会自动释放内存或资源,它只属于它指向的对象,优点是发生异常情况必须释放资源时,它简化了代码(自动帮你释放了资源)。
1 | auto anEmployee = std::make_unique<Employee>;// make_unique 用来创建 unique_ptr |
shared_ptr
shared_ptr
允许数据的分布式“所有权”,每次指定 shared_ptr
时,都递增一个引用计数,指出数据又多了一个“拥有者”。shared_ptr
超出作用于或者被删除时,就递减引用计数。当引用计数为0时,就表示数据不再有任何拥有者,于是释放指针引用的对象。
同 unique_ptr
类似,可以使用 make_shared
来创建shared_ptr
。
weak_ptr
使用weak_ptr
可以观察shared_ptr
,而不会递增或递减所链接shared_ptr
的引用计数。
总的原则就是,默认情况下使用 unique_ptr
,如果有共享需求就使用shared_ptr
。
==注意新的 c++标准已经废弃了auto_ptr
,所以不要再使用它。==
对 std::string 的几点补充
std::string 字面量
当使用 auto 来表明字符串常亮时,如果希望 auto 推断出的是 std::string 类型,可以使用如形式:
1 | auto string1 = "Hello World"; // auto 被推断为 char* 类型 |
类型转换函数
std 命名空间包含很多辅助函数用于字符串和数值之间的转换,如:
1 | // to_string 的各种重载 |
原始字符串字面量
通常我们定义字面量时如果含有特殊符号,需要用到转移符(比如\"
用来转移一个双引号)。原始字符串字面量可以让字符串不使用转移符而直接使用特殊符号,这个功能在操作数据库查询字符串和正则表达式时尤其有用。如:
1 | string str = R"(Hello, "World"!)"; |
使用 delete 标记函数不可用
某些情况下我们想禁用类的拷贝构造函数或者赋值函数,有两种方式,一种是将这两个函数声明为 private,并不提供任何实现。另外一种方式就是标记为 delete,这样当外部使用这两个函数的时候,编译器就会报错。如:
1 | class CMyClass |
在类中使用引用成员变量
在类中使用引用成员变量,必须注意只能在构造函数的初始化列表或者拷贝构造函数的初始化列表中初始化这个引用。
mutable 的使用
mutable 是为了告诉编译器在 const 类型的函数中,允许改变这个值,用法如下:
1 | // 假设以下代码在某个类中 |
typedef的另一种实现方法
using string = std::string;
等效于typedef std::string string;
使用 final 来禁用继承和重写
C++允许将类标记为 final,当继承这个类的时候,编译器就会报错,格式为:class CMyClass final{}
。
同理,也可以将函数标记为 final,这样子类就无法重写虚函数,格式为:virtual void MyMethod() final;
。
这样当子类出现virtual void MyMethod() overide;
时,就会报错。
重写基类的方法一定要加上 override
override 告诉编译器,我这个函数就是重写父类的,如果父类没有匹配的虚函数,那么就报错。如果没有加上 override,那么当父类没有这个函数的时候,就是在排上类中增加了一个新的虚函数了。
使用基类的构造函数构造派生类对象
有时候基类提供了带参数的构造函数,目的是让派生类始终能用到这个构造函数,并提供相应的参数来构造对象。这种情况下可以在派生类中显示的继成基类的构造函数,如:
1 | class CBase |
我在 vs2013上测试这个功能时,发现始终调用的是派生类的拷贝构造函数,比如上述例子中,编译器提示 CDriverd(const CDriverd&)的第一个参数类型 int 无法转换成 const CDriverd& 类型。即使我把拷贝构造函数禁用掉,也一样。暂时还不知道为什么。
另外使用这个特性要注意,显示继承只能继承全部,不能要求之继承某一个。
重写具有默认参数的函数,要注意默认参数不会被继承
1 | class Super |
上面的示例,当使用Super 的指针或者引用指向Sub 的实例调用 go 函数时,输出内容为sub:i = 2
。这是因为 C++根据描述对象的表达式类型在编译时绑定默认参数,而不是根据实际的对象类型绑定参数。所以为了避免这种情况,建议在派生类中的默认参数和基类保持一致。