C++中的虚函数表、强制类型转换、I/O流等——学习笔记
  ZIUh1hDjqPtV 2023年11月02日 66 0


一、虚函数表
什么是虚函数表,在C++的类中,一旦成员函数中有虚函数,这个类中就会多一个虚函数表指针,这个指针指向一个虚函数表,表里面记录了
这个类中所有的虚函数,当这个类被继承,它的子类中也会有一个虚函数表(不管子类中有没有虚函数),如果子类的成员函数中有函数签名与父
类的虚函数一样,就会用子类中的函数体寒它在虚函数表中的位置,这样就达到了覆盖的效果。
当通过类指针或引用调用函数时,会根据对象中实际的虚函数表记录来调用函数,这样就达到了多态的效果。

二、虚析构
当使用delete释放一个父类指针时,不管实际指向的对象是子类还是父类都只会调用父类的析构函数(多态肯定会出现的问题)。
如果子类的析构函数有需要负责释放的内存,就会造成内存泄漏。
为了解决这个问题,可以把父类的析构函数设置为虚函数,析构函数进行覆盖时不会比较函数名。

当父类的析构函数为虚函数时,通过父类指针或引用释放子类对象时,会自动调用子类的析构函数,子类的析构函数执行完成后也会调用父类的析构函数。
注意:析构函数可以是虚函数,但构造函数不行

三、强制类型转换
注意:C++中为了兼容C语言,(目标类型)源类型 依然可以继续使用,但C语言的强制类型转换安全性差,因此建议使用C++中的强制类型转换。
注意:C++之父认为如果代码设计的完善,根本不需要用到强制类型转换,而C++的强制类型转换之所以设计的很复杂,是为了让程序员多关注代码本身的设计,尽量少使用。

C++中的强制类型转换保证没有很大安全隐患。

static_cast<目标类型>(源类型)   编译器会对源类型和目标类型做兼容性检查,不通过则报错。
dynamic_cast<目标类型>(源类型)  编译器会对源类型和目标类是否同为指针或引用,并且存在多态型的继承关系。
const_cast<目标类型>(源类型)    编译器会对源类型和目标类检查,是否同为指针或引用,除了常属性外其他必须完全相同,否则报错。
reinterpret_cast<目标类型>(源类型)  编译器会对源类型和目标类是否为指针或整数进行检查,也就是说把整数转换成指针或把指针转换
为整数。

拓展:
静态编译:指针或引用的目标是确定的,在编译时期就确定了所有的类型检查、函数调用。
动态编译:指针或引用的目标是不确定的(多态),只有在函数调用的时候才确定具体是哪一个子类。

四、I/O流
I/O流的打开模式:
ios::in 以读权限打开文件,不存在则失败,存在不清空
ios::out 以写权限打开文件,不存在则创建,存在则清空
ios::app 打开文件用于追加,不存在则创建,存在不清空
ios::binary 以二进制模式进行读写
ios::ate 打开时定位到文件末尾
ios::trunc 打开文件时清空
fstream/ifstream/ofstream 类用于进行文件操作。
构造函数或成员函数 open 用于打开文件
good成员函数检查流是否可用
eof成员函数用于输入流是否结束

操作符 >> 用于从文件中读取数据到变量
操作符 << 用于输出数据到文件

IO流有一系列格式化控制函数,类似:左对齐、右对齐、宽度、填充、小数点位数。

#include <iostream>
#include <fstream>
using namespace std;

int main()
{
	fstream fsi("test.txt",ios::in);
	//fs.open("test.txt",ios::in);
	if(!fsi.good())
	{
		cout <<"打开失败"<<endl;
	}
	else
	{
		cout <<"打开成功"<<endl;
	}


	string str,s1,s2,s3;
	int num = 0;
/*	fsi >> str;	//读到空格或换行就停止
	fsi >> num >> s1 >> s2 >> s3;
	cout<<str<<"-"<<num<<"-"<<s1<<"-"<<s2<<"-"<<s3<<endl;
*/

	string arr[10];
	int i = 0;
	while(1)
	{
		fsi >> arr[i];
		if(arr[i].size() == 0)
		{
			break;
		}
		i++;
	}

	for(int j=0; j<i; j++)
	{
		cout << arr[j] <<"-";
	}

	fstream fso("test.txt",ios::out);
	fso << "hehe" << " " << 100 <<" " <<"adsadsad"<<endl;
}

二进制读写:read/write
read (char_type *__s,streamsize __n)
write (char_type *__s,streamsize __n)

gcount成员函数可以获取上次流的二进制读写操作的字节数。

随机读写:
seekp (off_type,ios_base::seekdir)
功能:设置文件的位置指针。
off_type:偏移值
正值向右,负值向左
seekdir:基础位置
ios::beg 文件开头
ios::cur 当前位置
ios::end 文件末尾

#include <iostream>
#include <fstream>
using namespace std;
int main()
{
	fstream fs("test.txt",ios::in);
	if(!fs.good())
	{
		cout << "文件打开失败" << endl;
		return -1;
	}
	// 调整文件的位置指针到末尾
	fs.seekp(0,ios::end);
	cout << "文件的字节数:" << fs.tellp() << endl;
	fs.close();
}

练习:使用C++标准IO,实现带覆盖检查的cp命令。
./cp src dest

#include <iostream>
#include <fstream>
using namespace std;

int main(int argc,char* argv[])// 写的有点问题0.0
{
	if(argc != 3)
	{
		cout << "命令错误" << endl;
	}
	// 读写
	fstream fi(argv[1],ios::in);
	fstream fo(argv[2],ios::out);
	if(!fi.good())
	{
		cout << "源文件不存在" << endl;
	}
	cout << "是否要覆盖目标文件,y/n" << endl;

	while(1)
	{
		string a;
		cin >> a;
		if(a == "y")
		{
			break;
		}

		else if(a == "n")
		{
			return 0;
		}

		else
		{
    		cout << "指令错误" << endl;
			continue;
		}
	}

	while(1)
	{
		string str;
		fi >> str;
		if(str.size() == 0)
		{
			break;
		}
		fo << str <<" "; // 文件末尾多个空格,需要删除,并且没有换行功能
	}
}

五、类型信息 typeid
用于获取数据的类型信息。
name成员函数,可以获取类型的名字,内建类型名字使用缩写。
同时还支持 == != 用来比较是否是同一种类型。

如果用于判断父子类的指针或引用,它不能准确判断出实际的对象类型。但可以判断出具有多态继承关系的父子类的指针或引用,它的实际对象。

#include <iostream>
#include <typeinfo>
using namespace std;

class Base
{
public:
	virtual ~Base(void)
	{
	}
};

class Test:public Base
{

};

int main()
{
	Base b;
	Test t;
	cout << typeid(b).name() << endl;
	cout << (typeid(t) == typeid(b)) << endl;

	cout << endl;
	Base* p = new Test;
	cout << (typeid(*p) == typeid(Test)) << endl;
	cout << (typeid(p) == typeid(Test*)) << endl;

}
扩展:
    sudo find / -name filename
    sudo find / | grep "std"
    grep 'Base' *               当前目录查找包含此字符的文件
    grep -r 'Base' *            当前目录及所有子级目录,查找包含此字符的文件
    grep -r 'Base' * dir        指定目录下及所有子级目录,查找包含此字符的文件

六、异常处理
抛异常
throw 数据
抛异常对象
抛基本类型
注意:不能抛出局部对象的指针或引用(构造函数和析构函数不能抛出异常)。
注意:如果异常没有被捕获处理,程序就会停止。
捕获异常

try{
        可以抛出异常的代码
    }
    catch(类型 变量名) // 根据数据类型进行捕获
    {
        处理异常,如果无法处理可以继续抛出异常
    }

注意:捕获异常的顺序是自上而下的,而不是最精准的匹配,针对子类异常捕获时要放在父类的前面。

函数的异常声明:
返回值类型 函数名(参数列表)throw(类型1,类型2,…)
注意:如果不写异常声明表示什么类型的异常都可能抛出。
注意:如果写了异常声明表示只抛出某些类型的异常,一旦超出异常声明的范围,程序会直接停止,无法捕获。
注意:throw() 表示什么类型都不会抛出

设计异常类:

class Error
  {
      int errno;
      char errmsg[255];
  public:
      Error(int errno = -1,const char* msg = "未知错误")
      {
          this->errno = errno;
          strcpy(errmsg,msg);
      }
      int getError(void)
      {
          return errno;
      }
      const char* getErrmsg(void)
      {
          return errmsg;
      }
  }


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  1SoRi6M80lAM   2023年11月02日   51   0   0 c++C#mysqlC
  4kahY0lX0gEa   2023年11月02日   110   0   0 c++面向对象g++
  b150W9sC7g95   2023年11月02日   140   0   0 c++SQL ServerC#CSybase
  B2JnUQ2gB4jN   2023年11月02日   105   0   0 c++scalaSIMDi++
  kPias7KVf0i6   2023年11月02日   35   0   0 c++
ZIUh1hDjqPtV