目录
{ }列表初始化
内置类型---对单值变量及数组的初始化
列表初始化时进行的类型转换
自定义类型---对类对象或结构的初始化
initializer_list
1. 定义接受 initializer_list 参数的构造函数
2. 在函数中使用 initializer_list 参数
3. 使用 initializer_list 与 vector 等容器
用于推断类型的关键字
auto 和 decltype
用于管理虚方法的说明符
final 和 override
final
1. 修饰类
2. 修饰虚函数
3. 修饰非虚成员函数
override
{ }列表初始化
内置类型---对单值变量及数组的初始化
下面是将花括号列表初始化用于单值变量的情形 . 如下代码:
可以看出 变量 num1 和 变量num2 都被初始化为6 ;因此,采用大括号这种初始化方式可使用等号(=),也可以省略不写。
那如果花括号里我们什么都不写呢?那么它的值会是什么。如下代码:
如上可看到,花括号中如果什么都不写,这种情况下,变量将被初始化为 0
将花括号列表初始化用于数组初始化的情形 ,数组在之前就可使用列表初始化,但c++11对列表初始化新增了一些功能。
1. 初始化数组时可以省略等号(=);
2. 在花括号内可以不包含任何东西,此时,所有元素都被初始化为0,这与单值变量初始化类似;
如下代码:
此外,对列表初始化语法用于 new表达式中:
前两行只是申请了内存空间(即对于单个对象申请了一个int型大小的空间,对于多个对象申请了连续的4个大小的int型空间);后两行既申请了空间又对其进行了初始化。
列表初始化时进行的类型转换
c++11这种列表初始化常用于复杂的数据类型提供值列表,相较于c(内置类型)的初始化方式来说,c++11列表初始化对类型转换的要求更加严格。具体来说就是不允许把更宽的类型转换为窄的类型,比如:int-->char 不允许(当int的范围超出char时)。如下代码演示:
通过上面的代码可以看到,162行是 c 风格的初始化,把一个int类型的值为3556(已超出char的范围)赋给char类型变量,编译器只给了警告:发生截断,但还是可以通过的;而使用c++11的大括号{}列表初始化编译器就会直接报错,是不能通过编译的。因此,使用列表初始化时要注意类型间的转换
说明:列表初始化是禁止大类型向小类型转换的,上面是单值对象,数组也是一样的
自定义类型---对类对象或结构的初始化
将花括号列表初始化用于类对象,但要提供与某个构造函数的参数列表匹配的内容,并用大括号括起来。
花括号列表初始化可以用于类对象(要隐式类型转换,调用构造函数),是该类要支持单参数或多参数的构造函数(可以是带缺省值的) ;
说明:当用于new表达式 (想要申请并初始化多个对象) 时,如果申请的 对象个数 与 大括号中 初始化的个数不符(小于),这时,剩余的个数会去调用默认的构造函数初始化,此时需要有默认构造函数才行,否则会有编译错误:如:上面代码中 289 行,就需要提供默认构造函数才行,要么就对象个数与大括号中初始化数一致。
将花括号列表初始化用于结构体的情形
initializer_list
initializer_list 是一个模板类,它允许使用花括号初始化器({})来初始化对象。initializer_list 通常用于构造函数和函数参数,以接受任意数量的元素进行初始化。
这提供了一种方便的方式来初始化容器、数组或其他集合类型。
下面是initializer_list 的用法:
1. 定义接受 initializer_list 参数的构造函数
#include <iostream>
#include <vector>
#include <initializer_list>
using namespace std;
class MyClass
{
public:
MyClass(initializer_list<int> il)
{
for (auto& e : il)
{
data.push_back(e);
}
}
void print()
{
for (auto& e : v)
{
cout << e << " ";
}
cout << endl;
}
private:
vector<int> v;
};
int main()
{
MyClass mc = { 1, 2, 3, 4, 5 }; // 使用花括号初始化
mc.print(); // 输出: 1 2 3 4 5
return 0;
}
2. 在函数中使用 initializer_list 参数
#include <iostream>
#include <initializer_list>
using namespace std;
void print_list(initializer_list<int> il)
{
for (auto& e : il)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
print_list({ 10, 20, 30, 40, 50 }); // 输出: 10 20 30 40 50
return 0;
}
3. 使用 initializer_list 与 vector 等容器
vector
和其他STL容器通常都有接受initializer_list
参数的构造函数,因此,可以直接使用花括号初始化:
int main()
{
vector<int> v = { 1, 2, 3, 4, 5 };
for (auto& e: v)
{
cout << e << " ";
}
cout << endl; // 输出: 1 2 3 4 5
return 0;
}
说明:
1. initializer_list对象只用于初始化,它们不是真正的容器,不支持修改操作(如
push_back
)2. 如果类有多个构造函数,并且其中一些接受单个参数,可能会遇到与隐式类型转换相关的歧义。此时,可能需要使用
explicit
关键字来防止不期望的隐式转换
initializer_list
提供了一种方便且灵活的方式来初始化对象和集合,使得代码更加清晰和易读
用于推断类型的关键字
auto 和 decltype
关键字auto之前是一个存储类型的说明符,但c++11重新定义了auto的含义:将 其用于实现自动类型推断。要求必须进行显示初始化,让编译器将定义对象的类型设置为初 始化值的类型
关键字decltype, 把变量的类型声明为表达式指定的类型。
1. 若decltype 指定的表达式没有括号,则所声明变量的类型和指定表达式的类型相同
代码中所声明的变量的类型 z、u、w 与 decltype括号中所指定的类型一样
注意:虽然 m和n 都是引用,但表达式 m+n 不是引用,因此,mn 的类型是 int 而不是 int&
是两个int的和;
2. 若decltype 指定的表达式是函数调用,则所声明变量的类型和函数返回值的类型相同
注意:编译器是通过查看函数的原型 确定返回类型,而不会实际调用函数
3. 若decltype 指定的表达式是一个左值且带有括号,则所声明变量的类型是左值的引用
上面代码中,q 是被括号括起来的,则声明的变量 v 的类型是 左值 q 的引用,注意与无括号的对比;
注意:若用decltype 声明的变量 是上面代码中 323 行 的形式 就必须要引用一个左值,否则编译会报错;如:decltype((q)) v; //erro
说明:括号并不会改变表达式的值 和 左值性
用于管理虚方法的说明符
final 和 override
final
final 可用于修饰类、虚函数。final 关键字主要用于那些设计为不应该被继承或不应该被重写的类和方法。使用final 可以增强代码的可读性和可维护性,因为它明确指出了哪些类和方法是封闭的,不应该被修改或扩展。同时,它也有助于防止由于错误地继承或重写而引起的潜在问题
下面是
final
的几种用法:
1. 修饰类
当
final
用于类时,表示这个类不能被继承。这有助于防止不希望被继承的类被意外地继承
class Base final
{
// ... 类成员 ...
};
// 下面的代码会编译错误,因为Base类被声明为final
class Derived : public Base
{
// ... 类成员 ...
};
2. 修饰虚函数
当 final 用于虚函数时,表示这个虚函数在派生类中不能被重写。这有助于确保某些关键的函数行为在类的继承体系中保持不变
class Base {
public:
virtual void func() final
{
// ... 函数的实现
}
// ... 其他成员 ...
};
class Derived : public Base {
public:
// 下面的代码会编译错误,由于 func()函数在Base类中被声明为final
void func() // ... 想要重写的函数实现 ...
{
// ... 函数的实现
}
};
3. 修饰非虚成员函数
虽然非虚成员函数也可以使用final关键字,但这通常没有太多实际意义,因为非虚成员函数本来就不能被重写。
override
override关键字用于显式地指示一个成员函数在派生类中重写了基类中的虚函数。使用override的好处在于,它提供了编译时的检查,确保你确实重写了基类中的某个虚函数。如果基类中没有相应的虚函数,编译器会报错,从而防止潜在的错误。
下面是override的用法:
class Base {
public:
virtual void func1()
{
// 基类虚函数的实现
}
// 非虚函数,不能被重写
void func2()
{
// ...
}
};
class Derived : public Base {
public:
// 使用override关键字显式地表示我们重写了Base类的func1函数,
// 也可以不使用override,但使用override可以提供额外的检查
void func1() override
{
// 派生类重写func1函数的实现
}
// 尝试重写Base类的func2函数,但由于func2不是虚函数,这里会编译错误
// 即使使用了override关键字,也无法将其变为虚函数
void func2() override
{
// 尝试重写的实现,但会导致编译错误
}
};
在上面的演示中,Derived类继承了Base类,并重写了func1这个虚函数。通过在派生类的成员函数声明后添加override关键字,告诉编译器这个函数是重写的,并且编译器会检查基类中是否存在相应的虚函数。如果基类中没有相应的虚函数,编译器将报错。
注意:
即使不使用override关键字,只要函数签名(包括返回类型和参数列表)与基类中的虚函数匹配,派生类中的成员函数也会被视为重写了基类的虚函数。但是,使用override提供了额外的安全性,因为它强制要求基类中存在一个可重写的虚函数。
只有虚函数才能被重写。如果尝试重写非虚函数,即使使用了override关键字,也会导致编译错误