c++知识点总结
接上文
一、继承
1.2.2.2.2 委托构造
任何一类的构造函数都可以调用这个类的另一个构造函数
#include <iostream>
using namespace std;
// 基类
class Father
{
private:
string name;
public:
Father()
{
name = "张";
}
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类
class Son:public Father
{
public:
// 编译器会自动添加构造函数。透传构造
// Son():Father(){}
// 手动添加构造函数。委托构造
Son():Son("王"){}
Son(string fn):Father(fn){}
};
int main()
{
Son son;
cout << son.get_name() << endl; // 王
Son son1("张");
cout << son1.get_name() << endl; // 张
return 0;
}
1.2.2.2.3 继承构造
只是一种用法的名称,不代表构造函数是可以继承的。这是C++11的新增特性,只需要一句话,就可以实现派生类所有构造函数的透传构造,且透传到基类构造函数时,选择参数相同相同的构造函数。
#include <iostream>
using namespace std;
// 基类
class Father
{
private:
string name;
public:
Father()
{
name = "张";
}
Father(string name):name(name){}
string get_name()
{
return name;
}
};
// 派生类
class Son:public Father
{
public:
using Father::Father;
// 上面的一句话补充下面的代码
// Son():Father(){}
// Son(string name):Father(name){}
};
int main()
{
Son son;
cout << son.get_name() << endl; // 张
Son son1("张");
cout << son1.get_name() << endl; // 张
return 0;
}
1.1 对象创建与销毁的流程
#include <iostream>
using namespace std;
/**
* @brief The Value class
* 成为其他类的变量
*/
class Value
{
private:
string name;
public:
Value(string name):name(name)
{
cout << name << "创建了" << endl;
}
~Value()
{
cout << name << "销毁了" << endl;
}
};
class Father
{
public:
static Value s_value; // 静态成员变量
Value value = Value("Father的成员变量");
Father()
{
cout << "Father类的构造函数" << endl;
}
~Father()
{
cout << "Father类的析构函数" << endl;
}
};
Value Father::s_value = Value("Father的静态成员变量");
class Son:public Father
{
public:
static Value s_value; // 静态成员变量
Value value = Value("Son的成员变量");
Son()
{
cout << "Son类的构造函数" << endl;
}
~Son()
{
cout << "Son类的析构函数" << endl;
}
};
Value Son::s_value = Value("Son的静态成员变量");
int main()
{
cout << "主函数开始" << endl;
// 局部代码块
{
Son s;
}
cout << "主函数结束" << endl;
return 0;
}
流程如下:
Father静态成员变量创建了
Son静态成员变量创建了
Father成员变量创建了
Father类构造函数创建了
Son成员变量创建了
Son类构造函数创建了
Son析构函数
Son成员变量销毁了
Father析构函数
Father成员变量销毁了
主函数结束
Son静态成员变量销毁了
Father静态成员变量销毁了
规律如下:
1、创建流程与销毁流程相反
2、静态成员变量生命周期为整个程序运行周期
3、同类型部分在创建时优先基类后派生类
1.2 多重继承
1.2.1 基本使用
之前的代码一个派生类只有一个基类,这是单继承。但是C++支持多重继承,一个派生类可以有多个基类。每个基类与派生类的关系都可以单独看做是一个单继承。
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "能坐着" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "能躺着" << endl;
}
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
return 0;
}
1.2.2 二义性
1.2.2.1 两个基类拥有重名成员
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "能坐着" << endl;
}
void position()
{
cout << "放在客厅" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "能躺着" << endl;
}
// 函数签名:编译器通过函数签名区分函数
// 函数名称、参数类型、参数数量
void position()
{
cout << "放在床" << endl;
}
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
// sb.position(); 错误
sb.Sofa::position();
sb.Bed::position();
return 0;
}
1.4.2.2 菱形继承
若一个A类有两个派生类B、C,B、C又作为基类拥有同一个派生类D,此时就形成了菱形继承。
#include <iostream>
using namespace std;
class Furniture
{
public:
void func()
{
cout << "家具" << endl;
}
};
class Sofa:virtual public Furniture
{
};
class Bed:virtual public Furniture
{
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
sb.func();
return 0;
}
虚继承原理:
在虚继承中增加了一套继承的比对机制,由编译器配合虚基类指针和虚基类表查询实现。
在上面的代码中,Sofa类和Bed类作为虚继承的基类内部会多出一个隐藏的成员变量——虚基类指针。
虚基类指针指向虚基类表,虚基类表在类Furniture中保存一份,全局共享。
虚继承时,SofaBed同时集成到了Sofa类的虚基类指针和Bed类的虚基类指针,在调用func()时,会通过这两个指针查表进行比对,如果发现是同一个函数,则调用一次。
1.3 权限
1.3.1 权限修饰符
在本类中 | 在派生类中 | 全局(eg.主函数) | |
private | √ | X | X |
protected | √ | √ | X |
public | √ | √ | √ |
1、公有继承
派生类无法访问继承基类的私有成员,基类的保护成员与公有成员集成到派生类中权限不变。
2、保护继承
派生类无法访问继承的基类私有成员,基类的保护成员与公有成员继承到派生类中变为保护权限。
3、私有继承
派生类无法访问继承基类中的私有成员,基类的保护成员与公有成员继承到派生类中变为私有权限。
2、 多态
2.1 函数覆盖 override
函数覆盖即在函数隐藏的基础上,使基类被隐藏的函数变为虚函数,是多态的前提
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
};
class Dog:public Animal
{
public:
void eat()
{
cout << "吃屎" << endl;
}
};
虚函数注意事项:
1、虚函数具有传递性,当某个基类成员函数成为虚函数之后,派生类中的同名函数也会自动变为虚函数
2、只有成员函数与析构函数可以被设置为虚函数
3、如果函数的声明与定义分离,只需在声明处使用virtual修饰
4、在C++11中,可以在派生类的覆盖函数上使用override关键字来检测覆盖的有效性,覆盖成功编译才能通过
2.2 多态的概念
1、静态多态
即编译时多态,函数重载和运算符重载属于此类多态,编译器在编译时会自行决定调用哪个函数
2、动态多态
后文中的多态指的是动态多态,即运行时多态,程序运行时自行决定调用关系
2.3 实现
多态的实现需要三个条件:
1、公有继承
2、函数覆盖
3、基类引用/指针 指向派生类对象
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
};
class Dog:public Animal
{
public:
void eat()
{
cout << "吃屎" << endl;
}
};
class Cat:public Animal
{
public:
void eat()
{
cout << "吃<・)))><<" << endl;
}
};
/**
* @brief test_eat测试多态的函数
* @param a 基类引用派生类对象
*/
void test_eat(Animal& a)
{
a.eat();
}
/**
* @brief test_eat测试多态的函数
* @param a 基类指针指向派生类对象
*/
void test_eat(Animal* a)
{
a->eat();
}
class Bird:public Animal
{
public:
void eat()
{
cout << "吃虫子" << endl;
}
};
int main()
{
Animal a;
Dog d;
Cat c;
Bird b;
test_eat(a);
// 多态
test_eat(b);
test_eat(c);
test_eat(d);
cout << "-----------------" << endl;
Animal* a2 = new Animal;
Bird* b2 = new Bird;
Cat* c2 = new Cat;
Dog* d2 = new Dog;
test_eat(a2);
// 多态
test_eat(b2);
test_eat(c2);
test_eat(d2);
delete a2;
delete b2;
delete c2;
delete d2;
return 0;
}
2.4 原理:虚函数表——动态类型绑定
如果一个类中有虚函数,这个类中就会有个虚函数表,类的对象通过虚函数指针在表中查找真正的调用地址
2.5 虚析构函数
在使用多态时,基类的析构函数不使用virtual修饰,则派生类的析构函数可能不会被调用
3、 C++11类型转换
1. static_cast 静态转换
常用于基本数据类型之间的转换
int x = 1;
// C++11
double y = static_cast<double>(x);
cout << y << endl;
但是static_cast没有运行时的检查来保证转换的安全性,例如精度丢失不会有警示
同时,static_cast还可以用在类层次的结构中,即基类与派生类指针或者引用的转换
- static_cast进行上行转换是安全的,即把派生类指针或引用转换为基类的。
- static_cast进行下行转换是不安全的,即把基类指针或引用转换为派生类的。
#include <iostream>
using namespace std;
class Father
{
public:
string a = "Father";
};
class Son:public Father
{
public:
string b = "Son";
};
int main()
{
// // 上行转换:派生类→基类
// Son *s1 = new Son;
// Father* f1 = static_cast<Father*>(s1);
// cout << f1->a << endl;
// cout << s1 << " " << f1 << endl; // 0x10b10f8 0x10b10f8
// // 下行转换:基类→派生类
// Father* f2 = new Father;
// Son* s2 = static_cast<Son*>(f2);
// // s2->b 输出随机,结果不定
// cout << s2->a << " " << s2->b << endl;
// 上行转换
Son s1;
Father& f1 = static_cast<Father&>(s1);
cout << f1.a << endl; // Father
cout << &s1 << " " << &f1 << endl; // 0x61fe84 0x61fe84
// 下行转换
Father f2;
Son& s2 = static_cast<Son&>(f2);
// Father 无法输出正确内容
cout << s2.a << " " << s2.b << endl;
return 0;
}
2. dynamic_cast 动态转换
主要用于类层次之间的上行/下行转换,上行转换时与static_cast效果相同;下行转换时多了一个类型检查功能,更加安全
#include <iostream>
using namespace std;
class Father
{
public:
virtual void test()
{
cout << "Father" << endl;
}
};
class Son:public Father
{
public:
void test()
{
cout << "Son" << endl;
}
};
int main()
{
// Father* f0 = new Son; // 构成多态
// Father* f1 = new Father; // 不构成多态
// Son* s0 = dynamic_cast<Son*>(f0);
// Son* s1 = dynamic_cast<Son*>(f1);
// cout << s0 << " " << s1 << endl; // 0x10310f8 0
// s0->test(); // Son
Son s;
Father f;
Father& f0 = s; // 构成多态
Father& f1 = f; // 不构成多态
Son& s0 = dynamic_cast<Son&>(f0);
// Son& s1 = dynamic_cast<Son&>(f1); // 运行终止
s0.test(); // Son
return 0;
}
3. const_cast 常量转换
const_cast用于添加或移除const修饰,以便于在一定情况下修改原本被修饰为常量的读一下,在正常情况下,应该尽量避免使用const_cast,而是考虑通过设计良好的接口或者其他编程手段来避免这样的类型转换。
4. reinterpret_cast 重解释转换
reinterpret_cast可以把内存里的值重新解释,这种转换风险极高,慎用。
4、 抽象类
1. 概念
一个无法创建对象的基类。
如果一个类中有纯虚函数,这个类一定是抽象类;如果一个函数是抽象类,这个类中一定有纯虚函数
纯虚函数与普通虚函数相比,只有函数声明且赋0,没有函数体
virtual 返回值类型 函数名(参数列表) = 0;
2. 定义
#include <iostream>
using namespace std;
/**
* @brief The Shape class
* 抽象类:为派生类制定算法框架
*/
class Shape
{
public:
// 纯虚函数声明
virtual void perimeter() = 0;
virtual void area() = 0;
virtual ~Shape() // 虚析构函数
{
}
};
int main()
{
// 【注意】抽象类型不能声明,包括返回值类型、包括参数类型、变量类型、转换类型。
// Shape s1; 错误
// Shape* s2 = new Shape; 错误
return 0;
}
3. 使用
在派生类中覆盖并实现基类纯虚函数的函数体即可将派生类变为普通类,正常实体化;当一个派生类没有完全实现基类中全部纯虚函数时依然不能被实体化,需要接着继承,在新的派生类中接着实现剩余的纯虚函数,直至基类所有纯虚函数都被实现,派生类可继续被实体化。
#include <iostream>
using namespace std;
/**
* @brief The Shape class
* 抽象类:为派生类制定算法框架
*/
class Shape
{
public:
// 纯虚函数声明
virtual void perimeter() = 0;
virtual void area() = 0;
virtual ~Shape() // 虚析构函数
{
}
};
/**
* @brief The Polygon class
* 抽象类
*/
class Polygon:public Shape
{
public:
void perimeter()
{
cout << "∑边长" << endl;
}
};
class Rectangle:public Polygon
{
public:
void area()
{
cout << "w*h" << endl;
}
};
void test_duotai(Shape& s)
{
s.area();
s.perimeter();
}
int main()
{
// Polygon p; 错误
Rectangle r;
test_duotai(r);
return 0;
}
二、异常处理
1. 什么是异常
异常是程序运行中产生的问题,并非语法问题,例如at函数越界。异常一旦出现且不被正确处理就会造成程序崩溃。处理异常有两种方式:1、抛出异常 2、捕获异常
2. 抛出异常
关键字throw,可以在任意代码块抛出异常,通常在会出现预设的逻辑问题处进行抛出,如果抛出没有被捕获,则会逐层向上传递,直至主函数,当依旧没有被捕获,程序就会崩溃
#include <iostream>
using namespace std;
double divide(double a,double b)
{
if(b == 0)
{
throw "除数不能为0";
}
return a/b;
}
int main()
{
cout << divide(1,0) << endl;
cout << "主函数执行完毕" << endl;
return 0;
}
3. 捕获异常
使用try-catch块捕获异常
try负责可能抛出异常的代码;catch用来匹配异常类型:不能正确匹配类型,相当于没有捕获成功,异常对象继续向上传递。正确匹配类型,捕获成功,此时直接跳转到catch块执行代码,catch块执行完毕后try-catch块结束,执行外层的代码。
#include <iostream>
using namespace std;
double divide(double a,double b)
{
if(b == 0)
{
throw "除数不能为0";
}
return a/b;
}
int main()
{
// 捕获成功
try
{
cout << divide(1,0) << endl;
cout << "A" << endl; // 不执行
}catch(const char* e)
{
// 弥补措施
cout << 1 << endl;
cout << "捕获成功!" << e << endl;
}
// 捕获失败
try
{
cout << divide(1,0) << endl;
cout << "B" << endl; // 不执行
}catch(string e)
{
// 弥补措施
cout << 1 << endl;
cout << "捕获成功!" << e << endl;
}
cout << "主函数执行完毕" << endl;
return 0;
}
4. 标准异常家族
在使用时需要引入头文件 #include <stdexcept>
5. 自定义异常
继承自标准异常,由程序员自己定义
#include <iostream>
#include <stdexcept> // 标准异常
using namespace std;
/**
* @brief The ZeroException class
* 自定义异常类继承标准异常类
*/
class ZeroException:public exception
{
public:
// throw()表示异常规格说明
// 告诉编译器此函数不会抛出异常
const char* what() const throw()
{
return "除数不能为0";
}
};
double divide(double a,double b)
{
if(b == 0)
{
throw ZeroException();
}
return a/b;
}
int main()
{
// 捕获成功
try
{
cout << divide(1,0) << endl;
cout << "A" << endl; // 不执行
}catch(const ZeroException& e)
{
// 弥补措施
cout << 1 << endl;
cout << "捕获成功!" << e.what() << endl;
}
cout << "主函数执行完毕" << endl;
return 0;
}
6. 基类捕获
可以捕获基类异常,提升异常类型匹配的概率
7. 多重捕获
catch块可以有很多个,形成类似switch-case的效果,当catch块大于1时,必须派生类异常在前,基类异常在后。
#include <iostream>
#include <stdexcept> // 标准异常
using namespace std;
void test(int i)
{
string s = "fdsfe";
cout << s.at(i) << endl;
}
void func(int n)
{
if(n < 0)
{
throw length_error("-1");
}
if(n > 10)
{
throw bad_exception();
}
for(int i=0;i<n;i++)
{
test(i);
}
}
int main()
{
try
{
func(11);
}catch(length_error& e)
{
cout << "捕获成功1," << e.what() << endl;
}
catch(out_of_range& e)
{
cout << "捕获成功2," << e.what() << endl;
}
catch(logic_error& e)
{
cout << "捕获成功3," << e.what() << endl;
}
catch(exception& e)
{
cout << "捕获成功4," << e.what() << endl;
}
cout << "主函数执行完毕" << endl;
return 0;
}