C++学习之路--5


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);
  • 基础类型不可以转换,失去精度,不安全都可以转换。
  • 发生多态时可以向下转换

常量转换

  • 常量指针被转化成非常量指针,并且仍然指向原来的对象
  • 常量引用被转换成非常量引用,并且仍然指向原来的对象
  • 使 c o n s t c a s t c o n s t \color{red}{注意:不能直接对非指针和非引用的变量使用const_cast操作符去直接移除它的const}

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();
    • 在这里插入图片描述
      \color{red}{ 直接派生类}
    异常名称 说 明
    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 类型。

l o g i c e r r o r \color{red}{ logic - error 的派生类: }

异常名称 说 明
length_error 试图生成一个超出该类型最大长度的对象时抛出该异常,例如 vector 的 resize 操作。
domain_error 参数的值域错误,主要用在数学函数中,例如使用一个负值调用只能操作非负数的函数。
out_of_range 超出有效范围。
invalid_argument 参数不合适。在标准库中,当利用string对象构造 bitset 时,而 string 中的字符不是 0 或1 的时候,抛出该异常。

r u n t i m e e r r o r : \color{red}{ runtime-error 的派生类:}

异常名称 说 明
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) //按行读取
```

猜你喜欢

转载自blog.csdn.net/weixin_43615373/article/details/90347510