C++11增加的语法特性非常多,没办法一 一介绍。本文主要介绍实际中比较实用的语法特性。
在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:
struct Pos
{
int _x;
int _y;
};
int main()
{
int a1[] = { 1, 2, 3, 4, 5 };
int a2[5] = { 0 };
Pos p = { 1, 2 };
return 0;
}
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自
定义的类型,使用初始化列表时,可添加等号(=),也可不添加
struct Pos
{
int _x;
int _y;
};
int main()
{
int x1 = 1;
int x2{ 2 };
int a1[]{ 1, 2, 3, 4, 5 };
int a2[5]{ 0 };
Pos p{ 1, 2 };
// C++11中列表初始化也可以适用于new表达式中
int* pa = new int[4] { 0 };
return 0;
}
创建对象时也可以使用列表初始化方式调用构造函数初始化
#include
using namespace std;
class Pos
{
public:
Pos(int x, int y, int z)
:_x(x),
_y(y),
_z(z)
{
cout << "Pos(int x, int y, int z)" << endl;
}
private:
int _x;
int _y;
int _z;
};
int main()
{
Pos p1(2022, 1, 1); // old style
// C++11支持的列表初始化,这里都会调用构造函数初始化
Pos p2{ 2022, 1, 2 };
Pos p3 = { 2022, 1, 3 };
return 0;
}
std::initializer_list
文档介绍:Click me
std::initializer_list
的类型:
class std::initializer_list
int main()
{
// the type of il is an initializer_list
auto il = { 10, 20, 30 };
cout << typeid(il).name() << endl;// class std::initializer_list
return 0;
}
std::initializer_list
使用场景:
std::initializer_list
一般是作为构造函数的参数,C++11对STL中的不少容器增加std::initializer_list
作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=
的参数,这样就可以用大括号赋值
#include
using namespace std;
int main()
{
vector<int> v = { 1,2,3,4 };
list<int> lt = { 1,2 };
// 这里{"apple", "苹果"}会先初始化构造一个pair对象
map<string, string> dict = { {"apple", "苹果"},
{"banana", "香蕉"} };
// 使用大括号对容器赋值
v = { 10, 20, 30 };
return 0;
}
namespace nb
{
template<class T>
class vector {
public:
typedef T* iterator;
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
// 注意使用typename:
// 编译器不知道iterator是静态变量还是内置类型,加typename声明是内置类型
typename initializer_list<T>::iterator lit = l.begin();
while (lit != l.end()) {
*vit++ = *lit++;
}
// 另一种方式
//for (auto e : l) // *vit++ = e;
}
vector<T>& operator=(initializer_list<T> l)
{
vector<T> tmp(l);
std::swap(_start, tmp._start);
std::swap(_finish, tmp._finish);
std::swap(_endofstorage, tmp._endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。
C++11中废弃auto原来的用法,将其用于实现自动类型推断。这要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型
#include
using namespace std;
void func()
{
cout << "func" << endl;
}
int main()
{
int i = 10;
auto p = &i;
auto pf = func;
cout << typeid(p).name() << endl;
cout << typeid(pf).name() << endl;
map<string, string> dict = { {"apple", "苹果"}, {"banana", "香蕉"} };
//map::iterator it = dict.begin();
auto it = dict.begin();
//cout << typeid(it).name() << endl;
return 0;
}
关键字decltype
将变量的类型声明为表达式指定的类型
// decltype的一些使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
decltype(t1 * t2) ret;
cout << typeid(ret).name() << endl;
}
int main()
{
const int x = 1;
double y = 2.2;
decltype(x * y) ret; // ret的类型是double
decltype(&x) p; // p的类型是const int*
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
F(1, 'a');// int
return 0;
}
由于C++中NULL
被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。
// 可能存在类型安全性问题,nullptr是更好的选择
int* ptr = NULL;
所以出于清晰和安全的角度考虑,C++11中新增了nullptr
,用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0 // 既是整型常量
#else
#define NULL ((void *)0) // 又是指针常量
#endif
#endif
示例:
#include
void foo(int* ptr) {
std::cout << "foo(int* ptr) called" << std::endl;
}
void foo(int value) {
std::cout << "foo(int value) called" << std::endl;
}
int main() {
foo(NULL); // 调用第二个foo
foo(nullptr);// 调用第一个foo
return 0;
}
用于遍历容器(如数组、向量、列表等)中的元素,或者遍历具有迭代器的数据结构
int arr[] = {1, 2, 3, 4, 5};
// 遍历数组
for (int e : arr) {
std::cout << e << " ";
}
final
关键字:final 关键字用于修饰类、函数或虚函数,表示它们不可被继承或重写。
当一个类被声明为 final 时,该类不能被其他类继承。
当一个成员函数被声明为 final 时,该函数不能在派生类中被重写。
使用 final 关键字可以在设计中明确禁止继承或重写,提高代码的安全性和可维护性
class Base final {
// ...
};
class Derived : public Base { // 编译错误,无法从 final 类继承
// ...
};
class Base {
public:
virtual void foo() final {
// ...
}
};
class Derived : public Base {
public:
void foo() override { // 编译错误,无法重写 final 函数
// ...
}
};
override
关键字:override 关键字用于显式指定派生类中的成员函数是对基类中虚函数的重写,可以提高代码的可读性和可靠性。
在派生类中,如果需要重写基类中的虚函数,使用 override 关键字可以确保函数的正确重写。
如果派生类中的函数声明使用了 override 关键字,但实际上并没有重写基类的虚函数,编译器将产生错误
class Base {
public:
virtual void foo() {
// ...
}
};
class Derived : public Base {
public:
void foo() override { // 显式重写基类的虚函数
// ...
}
};
注意,override 关键字只能应用于派生类中的虚函数,用于确保正确的函数重写。如果在非虚函数上使用 override 关键字,编译器将产生错误
override
错误使用:基类中的函数没有被声明为虚函数。
示例:
class Base {
public:
void foo() {
// ...
}
};
class Derived : public Base {
public:
void foo() override { // 错误,Base::foo() 不是虚函数
// ...
}
};
基类中的虚函数的声明与派生类中的重写函数的声明不匹配 (包括函数名称、参数列表和返回类型)。
示例:
class Base {
public:
virtual void foo(int x) {
// ...
}
};
class Derived : public Base {
public:
void foo(double x) override { // 错误,签名不匹配
// ...
}
};
没有基类中对应的虚函数。
示例:
class Base {
public:
virtual void foo() {
// ...
}
};
class Derived : public Base {
public:
void bar() override { // 错误,没有对应的虚函数 foo()
// ...
}
};
在这些情况下,使用 override
关键字会产生编译错误,提示开发者存在错误的重写或使用方式。检查上述情况可以确保正确使用 override
关键字,并在重写虚函数时提高代码的可靠性。
array、forward_list(单链表)和unordered系列
array是C++标准库中的容器类模板,array和原生数组(c风格数组)最大的区别是对于越界访问的处理: