1、多态
- 静态多态 函数重载
- 动态多态 虚函数 继承关系
- 静态联编 地址早绑定,编译阶段绑定好地址
- 动态联编 地址晚绑定, 运行时绑定地址
- 多态—>父类的引用或指针指向子类对象
- 如果父类中有了纯虚函数,子类继承父类,就必须要实现纯虚函数,并且父类无法实例化对象。virtual int getResult() = 0;(抽象类)
- 虚析构 virtual ~类名(){}
- 解决问题:通过父类指针指向子类对象释放时候不干净导致的问题。
- 纯虚析构 virtual ~类名 = 0; 类内声明、类外实现。抽象类,不能实例化。
- 向上类型转换,向下类型转换,多态
2、函数模板
类型参数化 泛涵编程 – 模板技术
- template //告诉编译器下面如果出现T不要报错,T是一个通用模板,模板必须可以推导出T,template < typename T>
template <class T>
void test(T &a, T &b)
{
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int a = 10;
int b = 20;
//自动类型推导
test(a,b);
//显示指定类型
test<int>(a,b);
cout << "a = " << a << " ; b = " << b << endl;
- 普通函数可以进行隐式类型转换,而函数模板不可以。
- 如果出现重载,优先使用普通函数调用,如果没有实现,出现错误。
- 如果想调用模板函数,可以使用空参数列表 print<>(a,b)
- 函数模板可以发生重载
- 如果函数模板可以产生更好的匹配,,那么优先使用模板函数。
- 局限性:
- 通过具体化自定义数据类型,解决自定义数据类型的函数调用。
template<> bool myComplate <Person>(Person &a, Person &b) { if (a.age == b.age) { return true; } return false; }
- 语法: template<> 返回值 函数名<具体类型>(参数)
类模板
- 写法 template<T …> 紧跟着类
- 与函数模板区别,可以有默认类型参数,函数模板可以进行自动类型推导
template<class NameType, class AgeType>
class Person
{
public:
Person(NameType name, AgeType age = int )//类模板可以有默认的数据类型
{
this->age = age;
this->name = name;
}
NameType name;
AgeType age;
};
void test()
{
//自动类型推导
Person<string, int> p1("fsdf",20);
}
-
成员函数一开始不会创建出来,而是在运行时创建。
-
显式指定类型 void dowork(Person< string, int> &p)
-
参数模板化 template <class T1, class T2>
-
整体模板化 template< class T>
-
类模板继承问题
- 基类如果是模板类,必须让子类告诉编译器基类中的T到底是什么类型
- 如果不告诉无法分配内存,编译不通过
- 利用参数列表 class Child : public Base < int>
-
类模板的类外实现成员函数
template < class T1, class T2> Person<T1, T2>::Person(T1 name, T2.age)
-
建议模板不要做份文件编写,可以写道一个类中即可,类内进行声明和实现,最后把后缀改为.hpp
-
友元函数类内实现
friend void print(Person<T1, T2>& p)
-
友元函数类外实现
-
friend void print<>(Person <T1, T2> &p1);//没有<>普通函数声明,加上<>模板函数声明。让编译器看到函数,并且看到person 类
#include <iostream>
using namespace std;
#include <string>
template <class T1, class T2> class Person;
template <class T1, class T2> void print(Person<T1, T2>& p);
template <class T1, class T2>
class Person
{
friend void print<>(Person<T1, T2>& p);
public:
Person(T1 name, T2 age)
{
this->m_name = name;
this->m_age = age;
}
private:
T1 m_name;
T2 m_age;
};
template<class T1, class T2>
void print(Person<T1, T2>& p)
{
cout << "姓名: " << p.m_name << ";年龄: " << p.m_age << endl;
}
void test()
{
Person<string, int> p1("张三", 100);
print(p1);
}
int main()
{
test();
}
- 类模板应用
3、类型转换
静态转换
- 基础类型转换 double d= static_case < double> (a)
- static< 目标类型>(原始对象)
动态转换
- Base * base = dynamic_cast< base*>(child);
- 基础类型不可以转换,失去精度,不安全都可以转换。
- 发生多态时可以向下转换
常量转换
- 常量指针被转化成非常量指针,并且仍然指向原来的对象
- 常量引用被转换成非常量引用,并且仍然指向原来的对象
4、异常
- 在可能抛出异常的地方抛出异常 throw
- 如果异常都没有处理,那么成员terminate函数,使程序中断。
try
{
myDevide(a,b);
}
catch(int) //捕获异常
{
cout << "异常" << endl;
}
catch(...) //捕获异常
{
cout << "异常" << endl;
}
自定义异常
class myException
{
public:
void printerror()
{
}
}
throw myException(); //抛出异常 匿名对象
catch(myException e) //捕获异常
{
e.printerror()
}
- 栈解旋
- 从try开始到throw抛出异常之前所有栈上的对象都会被释放。
异常接口声明:只能在qt或linux下使用,void func() throw(int) //只能抛出int 类型的异常,而throw() 不抛出任何类型的异常
-
异常生命周期
- MyException e,会多开销一份数据,调用拷贝构造
- MyException *e, 不new提前释放对象,new自己管理delete
- 推荐MyException &e ,一份数据
-
异常的多态使用
- 利用多态来实现printError同一个接口调用,抛出不同的错误提示。
-
使用系统提供的异常类
- #include < stdexcept> 引入头文件
- catch (out_of_range &e)
- cout << e.what();
异常名称 说 明 logic_error 逻辑错误。 runtime_error 运行时错误。 bad_alloc 使用 new 或 new[ ] 分配内存失败时抛出的异常。 bad_typeid 使用 typeid 操作一个 NULL 指针,而且该指针是带有虚函数的类,这时 抛出 bad_typeid 异常。 bad_cast 使用 dynamic_cast 转换失败时抛出的异常。 ios_base::failure io 过程中出现的异常。 bad_exception 这是个特殊的异常,如果函数的异常列表里声明了 bad_exception 异常,当函数内部抛出了异常列表中没有的异常时,如果调用的 unexpected() 函数中抛出了异常,不论什么类型,都会被替换为 bad_exception 类型。
异常名称 | 说 明 |
---|---|
length_error | 试图生成一个超出该类型最大长度的对象时抛出该异常,例如 vector 的 resize 操作。 |
domain_error | 参数的值域错误,主要用在数学函数中,例如使用一个负值调用只能操作非负数的函数。 |
out_of_range | 超出有效范围。 |
invalid_argument | 参数不合适。在标准库中,当利用string对象构造 bitset 时,而 string 中的字符不是 0 或1 的时候,抛出该异常。 |
异常名称 | 说 明 |
---|---|
range_error | 计算结果超出了有意义的值域范围。 |
overflow_error | 算术计算上溢。 |
underflow_error | 算术计算下溢。 |
- 自己编写异常类
- string转char* 方法 this->info.c_str();
- 自己的异常类需要继承于 exception
- 重写 虚析构 和what()
- 内部维护一个错误信息字符串
- 构造时候传入错误信息字符串,what()返回
5、输入和输出流
- 标准输入流
cin.get()一次只能读一个参数
cin.get(一个参数)读一个字符
cin.get(两个参数)可以读字符串,读取字符串时不会拿走换行符”\n“
cin.getline()读取换行符并抛弃
cin.ignore() 没有参数忽略一个字符,带参数N,代表忽略n个字符
cin.peek() 偷窥,偷看一眼a,然后再放回数据缓冲区,缓冲区中还是a
cin.putback() 放回
案例:让用户输入指定范围的数字,如果不正确重新输入
- cin.fail() 标志位 0-> 正常 1-> 不正常
- cin.clear()重置标志位
- cin.sync()清空缓冲区
int num;
std::cout << "请输入一个1到10的数字:";
while (true)
{
std::cin >> num;
if (num > 0 && num <= 10)
{
std::cout << "你输入的数字为: " << num << std::endl;
}
std::cin.clear(); // 重置标志位
std::cin.sync(); //清空缓冲区
std::cout << "标志位: " << std::cin.fail << std::endl;
}
- 标准输出流
- cout.flush() 刷新缓冲区 Linux下有效
- cout.put() 向缓冲区写字符
- cout.write() 从buffer中写入num个字节到当前输出流中
- 格式化输出
- 通过流成员
int number = 99; cout.width(20); cout.fill(*); cout.setf(ios::left); 输出内容左对齐 cout.unsetf(ios::dec));卸载十进制 cout.setf(ios::hex); cout.setf(ios::showbase);强制输出整数基数 0 0x cout.unsetf(ios::hex) cout.setf(ios::oct);
- 控制符格式输出
#include <iomanip> int num = 2; cout << setw(20); << setfill('"') << setiosflags(ios::showbase) << setiosflags(ios::left) << hex << num << endl;
- 文件读写
- 引入头文件 #include < fstream>
- opstream ofs("./test.txt",ios::out | ios::trunc);
- 写数据
//以输出的方式打开文件
ofstream ofs("./test.txt", ios::out | ios::trunc);
//后期指定打开方式
ofstream ofss;
ofss.open("./test.txt", ios::out | ios::trunc);
if (ofss.is_open())
{
cout << "打开成功" << endl;
}
ofs << "姓名: ";
- 读数据
```c
char buf[1024];
ifstream ifs("./test.txt", ios::in);
while (!ifs.eof()) //eof读到文件尾
{
ifs.getline(buf2, sizeof(buf2))
}
while (ifs >> buf) //按行读取
```