chapter13.拷贝控制
概述
控制类类型的对象的拷贝,赋值,移动,销毁包括五种特殊的成员函数(这些称为拷贝控制成员):
- 拷贝构造函数
- 拷贝赋值运算符
- 移动构造函数
- 移动赋值运算符
- 析构函数
拷贝构造函数
1.合成拷贝构造函数
如果一个类未定义自己的拷贝构造函数,编译器为我们定义的一个构造函数。即使我们定义了其他构造函数,编译器也会为我们合成一个拷贝构造函数。
拷贝赋值运算符
1.合成拷贝赋值运算符
析构函数
合成析构函数
使用default关键字
可以通过将拷贝控制成员定义为=default
来显示的要求编译器生成合成的版本。在类内用=default
修饰成员的声明时,合成的函数将隐式的声明为内敛的
阻止拷贝
使用delete关键字
可以将拷贝构造函数或者拷贝赋值运算符定义为删除的函数来阻止拷贝。在函数的参数列表后面加上=delete
。最好不要对析构函数指定为=delete,对于析构函数已经删除的类型,不能定义该类型的变量或者释放指向该类型动态分配对象的指针。(可以定义该类型的指针)
delete和default的区别:可以对任何函数指定为=delete,而只能对编译器可以合成的默认构造函数和拷贝控制成员使用=default
对象移动
1.右值引用
在新CPP标准中,可以移动对象而不是拷贝对象,移动而不是拷贝对象有时候可以大幅度提升性能。为了支持移动操作,新标准引入了一种新的引用类型叫做右值引用。右值引用就是必须绑定到右值的引用,并且只能绑定到一个即将被销毁的对象
note:1.常量左值引用可以绑定到右值
const int&& j = 100; // j是左值
const int& i = 100; // no problem
2.标准库函数move
该函数可以获得绑定到左值上的的右值引用,该函数定义在utility头文件中。
int j = 100;
int&& k = move(j);
3.移动构造函数和移动赋值运算符
- 不抛出异常的移动构造函数和移动赋值运算符都应该标记为noexcept.
- 如果没有移动构造函数,右值也会被拷贝。因为类类型 && 可以转化为 const 类类型&。
#include <iostream>
#include <vector>
#include <utility>
using namespace std;
class Example
{
private:
int x;
public:
Example():x(0){}
Example(const Example& ex)
{
this->x = ex.x;
}
int showX()const{return x;}
};
int main()
{
Example x;
Example y(x);
Example z(move(x)); // Example&& 转化为const Example&类型
cout << z.showX() << endl; // 0
return 0;
}
4.合成的移动构造函数和移动赋值函数
只有一个类没有定义任何自己的拷贝控制成员,且类的非静态成员变量都可以移动时,编译器才会为他合成移动构造函数和移动赋值函数。例如一个类定义了拷贝构造函数而没有定义移动构造函数,则该类的移动构造函数被定义为删除的。
移动操作和合成的拷贝控制成员间的相互作用关系:如果定义了一个移动构造函数或者移动赋值函数,则该类的合成拷贝构造函数和拷贝赋值运算符会被定义为删除的。
5.引用限定符&或者&&
新标准库中允许向右值或者左值赋值。引用限定符&指出左侧运算对象可以是左值(指出一个非静态成员函数可以用于左值还是右值的符号),引用限定符&&指出左侧运算对象可以是右值
string str1, str2;
str1 + str2 = "Hello"; // no problem
6.移动迭代器
迭代器适配器,它生成的迭代器在解引用时会得到一个右值引用。
chapter14.重载运算与类型转换
1.输出运算符
2.输入运算符
- 在执行输入运算符时可能发生下列错误:
1. 当读取操作到达文件末尾或者遇到输入流的其他错误
2. 当流含有错误类型的数据
3.算术和关系运算符
4.下标运算符
5.递增和递减运算符
1.定义前置递增运算符和前置递减运算符
为了与内置版本保持一致,前置运算符应该返回递增或者递减后对象的引用。
// 例如:
ClassName& operator++();
2.定义后置递增运算符和后置递减运算符
- 为了解决区分前置和后置运算符的问题,后置版本接受一个额外的int类型的形参(并不被使用)。
// 例如:
ClassName operator++(int);
- 为了与内置版本保持一致,后置运算符应该返回对象的原值。(递增或者递减之前的值)
- 显示的调用后置运算符
objectName.operator++(); // 调用前置版本的递增运算符
objectName.operator++(0); // 调用后置版本的递增运算符
6.成员访问运算符
- 解引用运算符*
- 箭头运算符->
7.函数调用运算符
如果类重载了函数调用运算符,则我们可以像使用函数一样使用该类的对象。 因为这样的类同时也能存储状态(和普通的类一样),所以和普通函数相比更加灵活。如果类定义了函数调用运算符,则该类的对象称为函数对象。该类叫做函数对象类
// 示例:返回一个整数的绝对值
class AbsInt
{
private:
string str = "HelloWorld";
public:
string getStr()const {return str;}
int operator()(int val)const
{
cout << getStr() << endl;
return val > 0 ? val : -val;
}
};
int main()
{
AbsInt number;
cout << number(-100) << endl; // 100
return 0;
}
// 示例:定义打印字符串到指定的输出流的类,其内容按照指定的分隔符分割
class PrintString
{
private:
ostream& out;
char sep;
public:
PrintString(ostream& out = cout, char sep = ','):out(out), sep(sep){}
void operator() (const string& str)const
{
out << str << sep;
}
};
int main()
{
// 匿名对象
PrintString()("HelloWorld"); // HelloWorld,
return 0;
}
8.函数对象
函数对象常常作为泛型算法的是实参
1. 函数对象的种类
- 重载了函数调用运算符的类的对象
- lambda表达式:当编写了一个lambda,编译器将该表达式翻译为一个未命名类的未命名对象,lambda表达式产生的类中含有一个重载的函数调用运算符。
vector<string>vec{"I", "hello", "hi", "example"};
// 找出第一个长度大于给定值2的字符串
size_t size = 2;
auto iter = find_if(vec.begin(), vec.end(), [size](const string& str) -> bool {
return str.size() > size;
});
cout << *iter << endl; // hello
2.标准库定义的函数对象
上面这些类定义在functional头文件中
- 在算法中使用标准库定义的函数对象的示例
9.可调用对象
1.可调用对象的种类
- 函数
- 函数指针
- lambda表达式
- bind函数创建的对象
- 重载了函数调用运算符的类定义的对象
调用形式
调用形式指明了调用返回的类型和传递给调用的实参类型。一种调用形式对应一个函数类型。例如:
int(int, int); // 一个函数类型
不同的可调用对象类型可能具有相同的调用形式
2.标准库function类型
function类定义在functional头文件中。
- function的操作
10.类型转换运算符
- 类型转换运算符:转换构造函数将实参类型的对象转化为类类型。而类型转换符则相反,它是类的一种特殊成员函数,将一个类类型的值转换为其他类型。
- 一般形式
// 类型转换运算符没有显示的返回类型和形参
operator type()const;
- 示例
// 使用类型转换运算符模拟实现Java中的包装类Integer,实现简单的拆箱机制
class Integer
{
private:
int val = 0; // Java类中的成员变量都具有默认值,整形为0
public:
Integer(){}
Integer(int val): val(val){}
// 定义类型转换运算符
operator int()const {return val;}
friend ostream& operator<<(ostream& out, const Integer& i)
{
out << i.val;
return out;
}
};
int main()
{
// 先调用转换构造函数,再调用拷贝构造函数
Integer i1 = 100;
cout << i1 << endl; // 100
// 隐式执行类型转换运算符,将i1转为100
int i = i1 + 100;
cout << i << endl; // 200
return 0;
}
- 显示的类型转换运算符:C++11引入关键字explicit
// 对上例子的改进如下:
explicit Integer(int val): val(val){}
// 定义类型转换运算符
explicit operator int()const {return val;}
// 先调用显示的转换构造函数,再调用拷贝构造函数
Integer i1 = Integer(100);
cout << i1 << endl; // 100
// 隐式执行显示的类型转换运算符,将i1转为100
int i = static_cast<int>(i1) + 100;
cout << i << endl; // 200
一般来说,定义了引入explicit关键字的显示类型转换运算符,需要通过显示的强制类型转换。这个规定有一个例外:当表达式被用作条件,编译器会将显示的类型转换自动用于他(显示的类型转换被隐式执行)
5. 类类型转化为bool
// cin 被istream类中定义个operator bool()类型转换函数隐式的执行转换
int number;
while (cin >> number)
chapter15.面向对象程序设计
1. override关键字的作用
CPP11新标准允许派生类显示的注明它使用某个成员函数覆盖了它继承的虚函数。具体做法是在形参列表后面加上一个关键字override。
2.派生类与基类之间的类型转换
- 派生类到基类的类型转换(只对指针或者引用有效):编译器会隐式的执行派生类到基类的类型转换。之所以存在派生类到基类的类型转换,因为每个派生类对象都包含一个基类部分,而基类的指针或者引用可以绑定到该基类部分上。
// son为Father类的子类定义的对象
Father* ptr = &son;
Father& far = son;
对于其他类型:,只有该派生类对象中的基类部分会被拷贝,移动,或者赋值,他的派生类部分将被忽略掉。
2. 不存在基类到派生类的自动类型转换
3.final关键字的作用
- 防止被其他类继承,CPP11新标准引入了一个关键字final,在类名后加上关键字final
- 将成员函数指定为final,将阻止子类覆盖该函数
4.动态绑定
动态绑定:当使用基类的指针或者引用时,并不清楚该指针或者引用所绑定对象的真实类型。(运行期才确定) 动态绑定只有当通过指针或者引用调用虚函数才发生,并且如果调用非虚函数不会发生动态绑定。这种情况下,对象的动态类型和静态类型才有可能不同。当通过具有普通类型的表达式(对象)调用虚函数时,在编译时就将调用的版本确定下来。
5.回避虚函数机制
在某些情况下,希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本。解决方案:使用作用域运算符
class Father
{
public:
virtual void eat()const {cout << "吃面" << endl;}
};
class Son : public Father
{
public:
void eat()const override
{
Father::eat(); // 指定执行父类的虚函数版本
cout << "吃饭" << endl;
}
};
int main()
{
Son son;
Father* ptr = &son;
ptr->eat();
return 0;
}
6.纯虚函数与抽象基类
- 一个纯虚函数无需定义,(也可以为纯虚函数提供定义,但是函数体必须定义在类的外面)例如:
virtual void eat()const = 0;
- 抽象基类:含有纯虚函数的类就叫做抽象基类。对于抽象基类,我们不能创建抽象基类的对象。
7.在继承中改变个别成员的可访问性
策略:在类的内部使用using声明语句
8.默认的继承访问级别
默认情况下,使用class关键字定义的派生类是私有继承的,而使用struct关键字定义的派生类是公有继承的。
8.继承体系中,基类中应该将析构函数定义为虚析构函数,以确保执行delete基类指针时执行正确的析构函数版本。
将基类中的析构函数定义为虚析构函数,让析构函数也有动态绑定的特性。和其他虚函数一样,析构函数的虚属性也会被继承。
class Father
{
public:
virtual ~Father() {cout << "father" << endl;}
};
class Son : public Father
{
public:
~Son() {cout << "son" << endl;}
};
int main()
{
Father* ptr = new Son();
// son father
// 派生类的基类部分由基类的析构函数完成析构
delete ptr;
return 0;
}
chapter16.模板与泛型编程
模板定义以关键字template开始,后跟一个模板参数列表。模板参数列表是一个逗号分隔的一个或者多个模板参数的列表。模板类型参数可以用来指定函数的返回类型或者函数的参数类型,以及在函数体内用于变量声明或者类型转换。
1.函数模板
- 非类型模板参数:除了定义模板类型参数,可以在模板中定义非类型模板参数,一个非类型参数表示一个值,而且通过特定的类型名指定非类型参数。非类型模板参数的模板实参必须是一个常量表达式。因此在需要常量表达式的地方可以使用非类型模板参数。(比如数组大小)
// 比较不同长度的字符串字面常量
template<unsigned int M, unsigned int N>
int compare(const char(&p1)[M], const char(&p2)[N])
{
return strcmp(p1, p2);
}
int main()
{
cout << compare("Hello", "Aorld") << endl; // 1
return 0;
}
- 函数模板和类模板成员函数的定义通常放在头文件中。
- 示例
template<typename T1>
int compare(const T1& v1, const T1& v2)
{
if (less<T1>()(v1, v2))
return -1;
if (less<T1>()(v2, v1))
return 1;
return 0;
}
int main()
{
cout << compare<const char*>("Hello", "Aorld") << endl; // 1
cout << compare<int>(1, 4) << endl; // -1
return 0;
}
2.类模板
chapter17.标准库特殊设施
1.tuple类型
一个tuple类型可以有任意数量的成员,当希望将一些不同数据类型或者相同数据类型的数据组合成单一对象,tuple很有用。
1.tuple的操作
2.tuple的定义和初始化
// 使用tuple的默认构造函数,对每个成员进行值初始化
tuple<int, bool> t1;
cout << get<0>(t1) << get<1>(t1) << endl; // 00
tuple<int, vector<int>, string> t2(100, {1,2}, "Hello");
// make_tuple函数根据初始值的类型推断tuple的类型
tuple<string, double>t3 = make_tuple("Hello", 12.0);
3.tuple的成员访问
使用get函数模板
4.使用tuple返回多个值
2.bitset类型
1.初始化bitset的方法
- 用unsigned值初始化bitset:当使用一个整型值初始化bitset时,这个值被转化为unsigned long long类型并被当作位模式来处理。
// b1比初始值小,初始值中的高位被抛弃
bitset<13>b1(0xbeef);
cout << b1 << endl; //1111011101111
// b2比初始值大,他的高位被置为0
bitset<20>b2(0xbeef);
cout << b2 << endl; //00001011111011101111
bitset<16>b3(0xbeef);
cout << b3 << endl; //1011111011101111
bitset<8>b2(15);
cout << b2 << endl; //00001111
- 从一个string初始化bitset:可以从一个string或者字符数组指针来初始化bitset
bitset<10>b1("11001111100");
cout << b1 << endl; //1100111110
string str = "11111111";
bitset<8>b2(str,4,4);
cout << b2 << endl; //00001111
2.bitset操作
置位的意思就是某个位被置为数值1.
3.随机数
c库函数rand()生成的是均匀分布的伪随机数。 定义在头文件random中的随机数库通过一组协作的类解决随机数问题:随机数引擎类和随机数分布类。一个引擎类可以生成unsigned随机数序列,一个分布类使用一个引擎类生成指定类型的,给定范围内的,服从特定概率分布的随机数
1.随机数引擎类
- 随机数引擎类是一个函数对象类,它定义了一个函数调用运算符,该运算符不接受参数并返回一个随机无符号整数。标准库定义了多个随机数引擎类,每个编译器会指定其中一个作为default_random_engine类型
// 示例
default_random_engine e;
// 产生10个随机无符号整数
for (size_t i = 0; i < 10; i++)
{
cout << e() << endl;
}
- 随机数引擎操作
2.分布类型和引擎
分布类也是一个函数对象类,定义了一个函数调用运算符,他接受一个随机引擎作为参数。分布对象使用它的引擎参数生成随机数,并将其映射到指定的范围。
// 生成0到9之间均匀分布的随机数
uniform_int_distribution<unsigned>u(0,9);
default_random_engine e;
// 产生10个随机无符号整数
for (size_t i = 0; i < 10; i++)
{
cout << u(e) << endl;
}
分布对象和引擎对象的组合称为随机数发生器
分布类型的操作:
3.随机数引擎类生成的数值序列
- 一个给定的随机数发生器一直会生成相同的随机数序列,一个函数内部如果定义了一个局部的随机数发生器,应该将引擎对象和分布对象定义为static的。否则每次调用函数将生成相同的序列。
// 两次调用test函数都生成相同的序列
void test()
{
// 生成0到9之间均匀分布的随机数
uniform_int_distribution<unsigned>u(0,9);
default_random_engine e;
// 产生10个随机无符号整数
for (size_t i = 0; i < 10; i++)
{
cout << u(e) << " "; //0 1 7 4 5 2 0 6 6 9
}
cout << endl;
}
int main()
{
test();
test();
return 0;
}
// 改进
将引擎对象和分布对象设置为静态的
4.设置随机数发生器种子
1. 希望每次运行程序生成不一样的随机结果,可以通过一个种子来达到这个目的。种子就是一个数值,引擎可以利用它从序列中一个新位置重新开始生成随机数。两个引擎如果使用相同的种子,则将生成相同的序列。
2. 为引擎设置随机数发生器种子的方式
1. 创建引擎对象时提供种子
2. 调用引擎对象的seed成员
- 示例
//default_random_engine e(10);
//default_random_engine e(time(nullptr));
default_random_engine e;
e.seed(time(nullptr));
// 产生10个随机无符号整数
for (size_t i = 0; i < 10; i++)
{
cout << e() << " "; //0 1 7 4 5 2 0 6 6 9
}
5.生成随机实数
定义一个uniform_real_distribution类型(其中一个分布类)的对象。并让标准库处理从随机整数到随机浮点数的映射
default_random_engine e(time(nullptr));
uniform_real_distribution<double> u(0,1);
for (size_t i = 0; i < 10; i++)
{
cout << u(e) << " ";
}
4.IO库再探
1.格式控制(格式化输入和输出)
标准库定义了一组操纵符(manipulator)来修改流的格式状态。格式状态用于控制格式化的某下方面,比如说整型值是几进制,浮点数的精度,一个输出元素的宽度等。
1.控制布尔值的格式
默认情况下,bool值打印成1或者0。即一个true值输出为整数1,而false输出为整数0。可以通过对流使用boolalpha和noboolalpha操纵符来控制布尔值的格式
cout << "default bool values:" << true << false << endl; //10
cout << boolalpha //该操纵符表示将true和false输出为字符串
<< true << false //truefalse
<< noboolalpha; //恢复流的初始/默认状态
2.指定整型值的进制
默认情况下,整型值的输入输出使用十进制。可以使用hex,oct,dec将其改为十六进制,八进制或者改回十进制。
showbase操纵符的作用:再输出结果中显示进制。
前导0x表示16进制
前导0表示八进制
无前导字符串表示十进制
cout << showbase; //打印整型值时显示进制
cout << "默认十进制" << 1024 << endl; //1024
cout << "十六进制" << hex << 1024 << endl; //0x400
cout << "十进制" << dec << 1024 << endl; //1024
cout << "八进制" << oct << 1024 << endl; //02000
cout << noshowbase << dec << endl; //恢复流的默认状态
3.控制浮点数格式
- 可以控制浮点数输出三种格式:
1.以多高精度打印浮点数
2.数值是打印为十六进制还是定点十进制还是科学计数法形式
3.对于没有小数部分的浮点值是否打印小数点
- 指定打印精度:可以通过调用IO对象的precision成员或者setprecision操纵符来改变精度。(setprecision操纵符定义在头文件iomanip中)
precision():返回当前精度
precision(int):将精度设为形参值
setprecision(int):将精度设为形参值
//浮点数的默认打印精度为6
cout << cout.precision() << " " << sqrt(2.0) << endl; // 6 1.41421
// 使用precision方法设置精度
cout.precision(4);
cout << sqrt(2.0) << endl; //1.414
// 使用setprecision操纵符设置精度
cout << setprecision(12) << sqrt(2.0) << endl; //1.41421356237
- 指定浮点数记数法:操纵符scientific改变流的状态来使用科学记数法,操纵符fixed改变流的状态来使用定点十进制。hexfloat强制浮点数使用十六进制,操纵符defaultfloat将流恢复到默认状态。浮点数有多种计数法:定点十进制,科学计数法,十六进制
cout << "默认格式:" << 100 * sqrt(2.0) << endl;
cout << "科学计数法:" << scientific << 100 * sqrt(2.0) << endl;
cout << "定点十进制:" << fixed << 100 * sqrt(2.0) << endl;
cout << "十六进制:" << hexfloat << 100 * sqrt(2.0) << endl;
//将流恢复到默认状态
cout << defaultfloat << 100 * sqrt(2.0) << endl;
- 打印小数点
// 默认情况下:当一个浮点数的小数部分为0时,不显示小数点
cout << 80.0 << '\n'; //80
cout << showpoint << 80.0 << '\n'; // 强制打印小数点,80.0000
cout << noshowpoint << 100.0 << endl; // 恢复小数点的默认格式,100
- 输出补白
setw(int):指出下一个数字或者字符串值的最小空间
left:左对齐输出
right:右对齐输出(默认格式)
setfill:允许指定一个字符代替默认的空格来补白输出
internal:控制负数的符号的位置,它左对齐符号,右对齐值,用空格填满所有中间空间。
int i = -16;
double d = 3.14159;
cout << setw(12) << i << "next row" << '\n';
cout << setw(12) << d << "next row" << '\n';
cout << "----------------------" << '\n';
cout << right << setw(12) << i << "next row" << '\n';
cout << setw(12) << d << "next row" << '\n';
cout << "----------------------" << '\n';
cout << left << setw(12) << i << "next row" << '\n';
cout << setw(12) << d << "next row" << '\n';
cout << "----------------------" << '\n';
cout << internal << setw(12) << i << "next row" << '\n';
cout << setw(12) << d << "next row" << '\n';
cout << "----------------------" << '\n';
cout << setfill('#') << setw(12) << i << "next row" << '\n';
cout << setw(12) << d << "next row" << '\n';
运行结果如下:
6. 控制输入格式:默认情况下,输入运算符会忽略空白符。
char ch;
//cin >> noskipws; // 令输入符读取空白符
while (cin >> ch)
cout << ch;
cin >> skipws; // 使得cin恢复默认行为,丢弃空白符
2.未格式化的输入输出操作
标准库提供了一组低层操作,支持未格式化IO。这些操作允许我们将一个流当作一个无解释的字节序列来处理。
- 单字节操作:每次一个字节的处理流这些操作会读取而不是忽略空白符。
// 输入和输出完全相同
char ch;
while (cin.get(ch))
{
cout.put(ch);
}
- 将字符放回输入流
- 从输入操作返回的int值
// 输入和输出完全相同
int ch;
while ((ch = cin.get()) != EOF)
{
cout.put(ch);
}
- 多字节操作
3.流随机访问
各种流类型通常都支持对流中数据的访问。istream和ostream通常不支持随机访问。
- seek函数和tell函数
seek用于重定位,tell用于获取位置。g版本表示get,读取数据,p版本表示put,写入数据。 - 只有一个标记:在一个流中只维护单一的标记,并不存在独立的读标记和写标记。
- 重定位标记:由于只有一个单一的标记,所以在读写操作间切换时,就必须进行seek操作来重定位标记。
// 将标记移到一个固定位置
seekg(new_position);
seekp(new_position);
// 将标记移动到给定起始点之前或者之后指定的偏移位置
seekp(offset,from);
seekg(offset,from);
- 访问标记:函数tellg和tellp返回一个pos_type值,表示流的当前位置。
ostringstream writeStr;
ostringstream::pos_type mark = writeStr.tellp();
- 读写同一个文件
chapter18:用于大型程序的工具
1.异常处理
1.异常抛出
当使用throw关键字抛出一条表达式时,该表达式的静态编译时类型决定了异常对象的类型。所以如果程序抛出的表达式类型来自某个继承体系,如果一条throw表达式解引用一个基类指针,而该指针实际指向的是派生类对象,则抛出的对象将被切掉一部分,只有基类部分被抛出。
chapter19:特殊工具与技术
1.控制内存分配
1.重载new和delete运算符
- new表达式的工作机理
string *sp = new string("helloworld");
string *arr = new string[10];
1. new表达式调用一个名为operator new或者operator new[]的标准库函数
2. 编译器运行相应的构造函数以构造这些对象,并传入初始值(如果有的话)
3. 对象被分配了空间并构造完成,返回一个指向该对象的指针。
- delete表达式的工作原理
delete sp;
delete []arr;
1. 对sp所指的对象或者arr所指的数组中的元素执行对应的析构函数
2. 编译器调用operator delete 或者operator
delete[]的标准库函数释放内存空间
- 标准库定义的operator new和operator delete接口
//new头文件中定义的一个nothrow这个常量对象
//可以请求调用new的非抛出版本
string *ptr = (string*)operator new(sizeof(string), nothrow);
operator delete(ptr, nothrow);
- 如果应用程序希望控制内存分配及释放的过程,则需要定义自己的operator new和operator delete函数
2.malloc函数和delete函数
void* operator new(size_t size)
{
if (void* mem = malloc(size))
return mem;
else
throw bad_alloc();
}
void operator delete(void* mem)
{
free(mem);
}
int main()
{
int* ptr = (int*)::operator new(sizeof(int));
::operator delete(ptr);
return 0;
}
3.定位new表达式
- 定位new表达式如下图所示:
2.运行时类型识别
运行时类型识别(run-time type identification,RTTI)的功能由两个运算符实现:
- typeid运算符:用于返回表达式的类型。
- dynamic_cast运算符:用于将基类的指针或者引用安全的转换为派生类的指针或者引用。
上述两个运算符的应用场景:想使用基类对象的指针或者引用执行某个派生类操作,并且该操作不是虚函数
1.typeid运算符
- typeid表达式的形式是:typeid(e),其中e可以是任意表达式或者类型的名字。typeid操作的结果是一个常量对象的引用,该对象的类型是type_info或者它的公有派生类类型。
- typeid是否需要在运行时检查决定了表达式是否会被求值,只有当类型含有虚函数时,编译器才会对表达式求值。反之,如果类型不含有虚函数,则typeid返回表达式的静态类型,编译器无需对表达式求值就知道表达式的静态类型。
- 示例
class Base
{
public:
virtual ~Base(){}
};
class Derived : public Base
{
public:
void show()const {cout << "HelloWorld" << endl;}
};
int main()
{
// 通过基类指针或者引用调用子类中的特有操作
Base* d = new Derived();
if (typeid(*d) == typeid(Derived))
{
Derived* tmp = dynamic_cast<Derived*>(d);
tmp->show();
}
return 0;
}
3.type_info类
- type_info类的操作
- 创建type_info对象的唯一途径:使用typeid运算符
int arr[] = {10,20};
int* ptr = arr;
cout << typeid(1024).name() << endl; //i
cout << typeid(arr).name() << endl; //A2_i
cout << typeid(ptr).name() << endl; //pi
4.枚举类型
枚举属于字面值常量类型。CPP中包含两种枚举:限定作用域的枚举类型和不限定作用域的。
- 定义限定作用域的枚举类型示例如下:
enum class open_modes {input, output, append};
- 定义不限定作用域的枚举类型如下:
enum color {red, blue, green};
- 默认情况下,枚举值从0开始,依次加一
enum class Color {red, green, blank};
enum open_modes {input = 1, output = 2};
Color color = Color::blank;
cout << (int)color << endl; // 2,枚举值默认从0开始
open_modes mode = output;
cout << (int)mode << endl; // 2
- 指定枚举的大小:在CPP11标准中,我们可以在
enum 枚举名
后面加上冒号和想在枚举类型中使用的枚举成员的类型.在默认情况下限定作用域的枚举类型的成员类型为int.
// 枚举成员的值超过了int的范围,将产生未定义的行为
enum open_modes {intvalue = 2147483648, output = 2};
// 指定enum的潜在类型
enum class Color : unsigned long long int {red, blank = 888888888888ULL};
Color color = Color::blank;
cout << (unsigned long long)color << endl;
- 枚举类型的前置声明
//不限定作用域的枚举类型的前置声明
enum open_modes : unsigned long long
//限定作用域的枚举类型可以使用默认成员类型int
enum Color;
5.类成员指针
成员指针指的是可以指向类的非静态成员的指针。
1.数据成员指针
数据成员指针:指向类中某个成员非静态变量的指针
class Example
{
private:
int x = 100;
public:
// 返回值为成员变量指针
static const int Example::* data() {return &Example::x;}
};
int main()
{
Example ex;
const int Example::* ptr = Example::data();
// 成员指针访问运算符:.*,->*
cout << ex.*ptr << endl; // 100
return 0;
}
2.成员函数指针
- 指向类的成员函数的指针就是成员函数指针。
- 成员指针函数表
- 将成员函数指针作为可调用对象的方法
1. 使用标准库模板function生成一个可调用对象
2. 使用men_fn生成一个可调用对象
3. 使用bind生成一个可调用对象
6.嵌套类
定义在另一个类的内部称为嵌套类
标签:函数,int,定义,笔记,运算符,C++,类型,Primer,cout From: https://www.cnblogs.com/xiaocer/p/16621519.html