【C++】C++11 (1): 列表初始化、decltype、final和override关键字

前言

C++11增加的语法特性非常多,没办法一 一介绍。本文主要介绍实际中比较实用的语法特性。

一、列表初始化

1. {} 初始化

在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;
}

2. std::initializer_list

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;
}
  • 模拟实现vector支持{}初始化和赋值:
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;
	};
}

二、auto、decltype关键字

1. auto

在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;
}

2. decltype

关键字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;
}

3. nullptr

由于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;
}


三、范围for

用于遍历容器(如数组、向量、列表等)中的元素,或者遍历具有迭代器的数据结构

int arr[] = {1, 2, 3, 4, 5};
// 遍历数组
for (int e : arr) {
	std::cout << e << " ";
}

四、final和override

1. 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 函数
        // ...
    }
};

2. override 关键字:

override 关键字用于显式指定派生类中的成员函数是对基类中虚函数的重写,可以提高代码的可读性和可靠性。

在派生类中,如果需要重写基类中的虚函数,使用 override 关键字可以确保函数的正确重写。

如果派生类中的函数声明使用了 override 关键字,但实际上并没有重写基类的虚函数,编译器将产生错误

class Base {
public:
    virtual void foo() {
        // ...
    }
};

class Derived : public Base {
public:
    void foo() override { // 显式重写基类的虚函数
        // ...
    }
};

注意,override 关键字只能应用于派生类中的虚函数,用于确保正确的函数重写。如果在非虚函数上使用 override 关键字,编译器将产生错误

  • override错误使用:
  1. 基类中的函数没有被声明为虚函数。

    示例:

    class Base {
    public:
        void foo() {
            // ...
        }
    };
    
    class Derived : public Base {
    public:
        void foo() override { // 错误,Base::foo() 不是虚函数
            // ...
        }
    };
    
  2. 基类中的虚函数的声明与派生类中的重写函数的声明不匹配 (包括函数名称、参数列表和返回类型)。

    示例:

    class Base {
    public:
        virtual void foo(int x) {
            // ...
        }
    };
    
    class Derived : public Base {
    public:
        void foo(double x) override { // 错误,签名不匹配
            // ...
        }
    };
    
  3. 没有基类中对应的虚函数。

    示例:

    class Base {
    public:
        virtual void foo() {
            // ...
        }
    };
    
    class Derived : public Base {
    public:
        void bar() override { // 错误,没有对应的虚函数 foo()
            // ...
        }
    };
    

在这些情况下,使用 override 关键字会产生编译错误,提示开发者存在错误的重写或使用方式。检查上述情况可以确保正确使用 override 关键字,并在重写虚函数时提高代码的可靠性。


五、新增容器

【C++】C++11 (1): 列表初始化、decltype、final和override关键字_第1张图片
array、forward_list(单链表)和unordered系列

array是C++标准库中的容器类模板,array和原生数组(c风格数组)最大的区别是对于越界访问的处理:

  • array提供了范围检查,可以确保访问数组元素时不会越界。
  • 原生数组没有内置的越界检查,访问越界可能导致未定义的行为,如访问无效内存区域或数据的损坏

你可能感兴趣的:(C++,c++,开发语言)