C语言核心编程中,如何通过类和对象实现长尾词的?

2026-04-11 23:512阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计5913个文字,预计阅读时间需要24分钟。

C语言核心编程中,如何通过类和对象实现长尾词的?

一、运算符重载+运算符重载:对已有的运算符重新定义,赋予其新的功能,以适应不同数据类型的数据运算。

二、加号+运算符重载+运算符重载:实现两个自定义数据类型对象的相加操作。

三、成员函数重载成员函数重载:在同一个类中,允许存在多个同名函数,但它们的参数列表(参数个数或参数类型)不同。

四、运算符重载+运算符重载:实现两个自定义数据类型对象的相加运算。

一、运算符重载

运算符重载:对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型


1、加号运算符重载

作用:实现两个自定义数据类型相加的运算


方法1、成员函数重载

class Person { public: Person operator+(Person& p) { Person temp; temp.m_a = this->m_a + p.m_a; temp.m_b = this->m_b + p.m_b; return temp; } int m_a; int m_b; }; void test() { Person p1; p1.m_a = 10; p1.m_b = 20; Person p2; p2.m_a = 10; p2.m_b = 20; Person p3 = p1 + p2; //等价于p1.operator+(p2) cout << "p3.m_a = " << p3.m_a << endl; cout << "p3.m_b = " << p3.m_b << endl; } int main() { test(); system("pause"); return 0; }

方法2、全局函数重载

//定义类 class Person { public: int m_a; int m_b; }; //全局函数重载 Person operator+(Person& p1,Person& p2) { Person temp; temp.m_a = p1.m_a + p2.m_a; temp.m_b = p1.m_b + p2.m_b; return temp; } void test() { Person p1; p1.m_a = 10; p1.m_b = 20; Person p2; p2.m_a = 10; p2.m_b = 20; Person p3 = p1 + p2; //等价于p1.operator+(p2) cout << "p3.m_a = " << p3.m_a << endl; cout << "p3.m_b = " << p3.m_b << endl; } int main() { test(); system("pause"); return 0; }

总结:

1、成员函数重载本质调用:Person p3 = p1.operator+(p2)

2、全局函数重载本质调用:Person p3 = operator(p1,p2)

3、运算符重载,也可以发生函数重载


2、左移运算符重载

作用:可以输出自定义数据类型

所以, 只能利用全局函数重载左移运算符


//左移重载 class Person { public: int m_a; int m_b; }; //全局函数重载 ostream& operator<<(ostream& cout, Person& p) { cout <<"p.m_a = "<<p.m_a <<"p.m_a = "<<p.m_b << endl;; return cout; } //测试 void test() { Person p1; p1.m_a = 10; p1.m_b = 20; cout << p1 << endl; } int main() { test(); system("pause"); return 0; }

扩展:如果我们需要打印的成员属性是私有的呢?全局函数无法访问,怎么重载呢?

答:对全局函数使用友元

//左移重载 class Person { public: //声明友元 friend ostream& operator<<(ostream& cout, Person& p); //构造函数赋初值 Person(int a,int b) { m_a = a; m_b = b; } private: int m_a; int m_b; }; //全局函数重载 ostream& operator<<(ostream& cout, Person& p) { cout <<"p.m_a = "<<p.m_a <<" p.m_a = "<<p.m_b << endl;; return cout; } //测试 void test() { Person p1(10,20);//调用有参构造函数 cout << p1 << endl; } int main() { test(); system("pause"); return 0; }

3、递增运算符重载

作用:通过重载,实现自己的数据类型递增


前置++:

//++运算符重载 class Person { public: //前置++ Person& operator++() { ++m_a; return *this; } int m_a = 0; }; ostream& operator<<(ostream& cout,Person p) { cout << p.m_a << endl; return cout; } //测试 void test01() { Person p; p.m_a = 100; cout << ++p << endl; //前置++ } int main() { test01(); system("pause"); return 0; }


后置++:

重载后置递增,需要用int代表占位参数,用于区分前置和后置递增

//++运算符重载 class Person { public: //后置++ Person operator++(int) { Person temp = *this; //记录操作前的结果 m_a++; return temp; } int m_a = 0; }; ostream& operator<<(ostream& cout,Person p) { cout << p.m_a << endl; return cout; } //测试 void test02() { Person p1; p1.m_a = 100; cout << "后置++:" << p1++ << endl;//后置++ cout << "后置++:" << p1 << endl; } int main() { test02(); system("pause"); return 0; }

总结:

1、前置++,返回对象自身(*this),且是用引用的方式返回

2、后置++,需要记录操作前的结果,返回记录的结果,并且是用值的方式返回(因为用于记录操作前的结果是一个临时变量,返回引用会出错)

3、后置++,在参数上需要用int 作为占位参数


4、赋值运算符重载

c++编译器至少给一个类添加4个函数

1、默认构造函数(无参,函数体为空)

2、默认析构函数(无参,函数体为空)

3、默认拷贝构造函数,对属性进行值拷贝

4、赋值运算符 operator=, 对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题


示例:

class Person { public: Person(int age) { m_a = new int(age); } ~Person() { if (m_a != NULL) { delete m_a; m_a = NULL; } } Person& operator=(Person& p) { //系统默认浅拷贝 //this->m_a = p.m_a; //深拷贝 if (this->m_a = NULL) { delete m_a; m_a = NULL; } this->m_a = new int(*p.m_a); //返回自身 return *this; } int* m_a = NULL; }; //测试 void test01() { Person p(10); Person p2(20); Person p3(30); cout << "赋值前:p1:" << *p.m_a << " p2:" << *p2.m_a << " p3:" << *p3.m_a << endl; p = p2 = p3; cout << "赋值后:p1:" << *p.m_a << " p2:" << *p2.m_a << " p3:" << *p3.m_a << endl; } int main() { test01(); system("pause"); return 0; }

5、关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行比较


示例:

//关系运算符重载 //1、== 2、 != class Person { public: Person(string name,int age) { m_Name = name; m_Age = age; } //重载 == int operator==(Person& p){ if (this->m_Age == p.m_Age && this->m_Name == p.m_Name) { return 1; } else return 0; } //重载 != int operator!=(Person& p){ if (this->m_Age != p.m_Age || this->m_Name != p.m_Name) { return 1; } else return 0; } string m_Name; int m_Age; }; //测试 void test01() { Person p("Tom",18); Person p2("Tom", 18); Person p3("Tom", 18); Person p4("Jiu", 18); if (p == p2) { cout << "p 和 p2 相等!" << endl; } else cout << "p 和 p2 不相等!" << endl; if (p3 != p4) { cout << "p3 和 p4 不相等!" << endl; } else cout << "p3 和 p4 相等!" << endl; } int main() { test01(); system("pause"); return 0; }

6、函数调用运算符重载

函数调用运算符 () 也可以重载

由于重载后使用的方式非常像函数的调用,因此称为仿函数

仿函数没有固定写法,非常灵活

示例:

//函数调用运算符重载 //打印类 class myPrint { public: void operator()(string name) { m_Name = name; cout << m_Name << endl; } string m_Name; }; void test() { myPrint mp; mp("Hello World"); } int main() { test(); system("pause"); return 0; }

二、继承

继承是面向对象的三大特征之一

在定义一些类的时候,如果下级别的成员除了拥有上一级的共性,还有自己的特性,这时候我们就可以选择继承,来减少重复代码


1、继承的基本语法

class 子类 :继承类型 父类

例如:

class son : public father{ //成员 };

注意事项:

1、子类又称:派生类,父类又称:基类

2、子类的成员包括从父类继承来的,以及自己增加的成员

3、继承过来的表现共性,增加的表现个性


2、继承方式

继承方式分三种:

1、公有继承(public):当一个子类公有继承父类时,父类的公有成员也是子类的公有成员,父类的保护成员也是子类的保护成员,父类的私有成员不能直接被子类访问,但是可以通过调用父类的公有和保护成员来访问。

2、保护继承(protected): 当一个子类保护继承父类时,父类的公有和保护成员将成为子类的保护成员。

3、私有继承(private):当一个子类私有继承父类时,父类的公有和保护成员将成为子类的私有成员。

3、继承中的对象模型

问题:从父类继承过来的成员,那些属于子类对象?

答:在父类中的非静态成员属性都会被子类继承,私有属性也会继承,只是无法访问。会被编译器隐藏。


扩展:利用开发人员命令提示工具查看对象模型

1、跳转盘符:E:(存放文件的盘符)

2、跳转文件路径: cd 具体路径下

3、查看命令:cl /d1 reportSinglcleClassLayout类名 "文件名"

4、继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数


问题:父类和子类的构造和析构顺序谁先谁后?

答:

1、先构造父类,在构造子类

2、析构与构造相反



可以看见,子类父类的顺序,同,一个类中的类成员顺序是一致的

5、继承同名成员处理方式

问题:当子类和父类出现同名的成员,如何通过子类或父类中同名的数据?

答:访问子类同名成员,直接访问;访问父类同名成员,需要加作用域。


注意事项:

1、如果子类中出现了和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,即,不可直接调用重载

解决方法:调用时,增加作用域即可

6、继承同名静态成员处理

静态成员和非静态成员出现同名时,处理方法一致

1、访问子类同名成员,直接访问

2、访问父类同名成员,需要加作用域

7、多继承语法

c++中允许一个类继承多个类

语法:class 子类 : 继承方式 父类1,继承方式 父类2...


多继承可能回引发父类中同名成员出现,需要加作用域区分,实际开发中,不建议使用

//多父类继承 class Base1 { //父类1 public: Base1() { m_b1 = 100; } int m_b1; }; class Base2 { //父类2 public: Base2() { m_b2 = 200; } int m_b2; }; class Base3 { //父类3 public: Base3() { m_b3 = 300; } int m_b3; }; //子类多继承 class Son : public Base1,public Base2, public Base3{ public: int m_s1 = 400; }; void test() { Son s; cout << "多继承子类字节数:" << sizeof(s) << endl; cout << "Base1 = " << s.Base1::m_b1 << endl;; cout << "Base2 = " << s.Base2::m_b2 << endl;; cout << "Base3 = " << s.Base3::m_b3 << endl;; cout << "Son = " << s.m_s1 << endl;; } int main() { test(); system("pause"); return 0; }

8、菱形继承

概念:

1、两个子类继承同一个父类

2、又有某个类同时继承两个子类

3、这种继承被称为菱形继承,或砖石继承


菱形继承的问题:

1、羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。

答:加以作用域区分!

2、羊驼继承自动物的数据继承了两份,导致资源浪费

答:利用虚继承(在继承之前加上关键字virtual,变为虚继承),可以解决!

//菱形继承 class BASE { //父父类 public: int m_a; }; //父类1 class Base1 :virtual public BASE {}; //父类2 class Base2 :virtual public BASE {}; //子类 class Son : public Base1, public Base2{}; void test() { Son s; s.Base1::m_a = 100; s.Base2::m_a = 200; cout << "父类1,base1 :" << s.Base1::m_a << endl; cout << "父类2,base3 :" << s.Base2::m_a << endl; cout << "子类:Son :" << s.m_a << endl; } int main() { test(); system("pause"); return 0; }

虚继承原理:子类继承父类的两个指针,指针通过偏移量,找到同一份数据。

三、多态

1、多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类:

1、静态多态: 函数重载 和 运算符重载 属于静态多态,复用函数名

2、动态多态: 子类和虚函数实现运行时多态

区别:

1、静态多态的函数地址早绑定 - 编译阶段确定函数地址

2、动态多态的函数地址晚绑定 - 运行阶段确定函数地址

示例:

//多态 //动物类 class Animal { public: //虚函数,用virtual关键字修饰的函数 virtual void speek() { cout << "动物说话..." << endl; } }; //猫类 class Cat : public Animal { void speek() { cout << "猫在说话..." << endl; } }; //狗类 class Dog : public Animal { void speek() { cout << "狗在说话..." << endl; } }; void doSpeek(Animal& ani) { //父类和子类类型可以相互转换 ani.speek(); } void test() { Cat c; Dog d; //调用执行说话函数 doSpeek(c); doSpeek(d); } int main() { test(); system("pause"); return 0; }


总结:

动态多态的满足条件:

1、有继承关系

2、子类重写父类的虚函数


区分重写和重载:

重写:函数返回值类型,函数名,参数列表完全相同

重载:函数的参数的数量,类型或位置不同

动态多态的使用:

父类的指针或引用指向子类对象

2、深入剖析多态原理:

Animal类内部结构:

Cat类内部结构:

1、未重写父函数

2、重写父函数后

当子类重写父类虚函数后,子类的虚函数表内部会替换成子类的虚函数地址,父类不发生改变。

3、纯虚函数和抽象类

纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;

但类中有了纯虚函数,这个类就被称为抽象类


抽象类特点:

1、无法实例化对象

2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例:

4、虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码


解决方式:将父类中的析构函数改为虚析构或者纯虚析构


虚析构和纯虚析构共性:

1、可以解决父类指针释放子类对象

2、都需要有具体的函数实现


虚析构和纯虚析构区别:

如果是纯虚析构,该类属于抽象类,无法实例化对象


虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

C语言核心编程中,如何通过类和对象实现长尾词的?

类名::~类名(){}


示例:

//虚析构和纯虚析构 class Base { public: Base() { cout << "Base构造函数调用" << endl; } virtual ~Base() = 0; virtual void func() = 0; }; Base:: ~Base(){ cout << "Base析构函数调用" << endl; } class Son :public Base { public: Son(string name) { m_name = new string(name); cout << "son构造函数调用" << endl; } ~Son() { cout << "son析构函数调用" << endl; if (m_name != NULL) { delete m_name; m_name = NULL; } } void func() { cout << *m_name << endl; } string* m_name; }; void test01() { Base* b = new Son("TOM"); b->func(); delete b; } int main() { test01(); system("pause"); return 0; }

使用虚析构或纯虚析构前:

使用虚析构或纯虚析构后:

四、文件操作

文件操作头文件:<fstream>

操作文件的三大流:

1、ofstream:写操作

2、ifstream:读操作

3、fstream:读写操作


1、文本文件

写文件

步骤:

1、包含头文件:#include<fstream>

2、创建流对象:ofstream ofs;

3、打开文件:ofs.open("文件路径",打开方式);

4、写数据:ofs<<"写入的数据"

5、关闭文件:ofs.close();

文件打开方式:

打开方式

解释

ios::in

为读文件而打开文件

ios::out

为写文件而打开文件

ios::ate

初始位置:文件尾

ios::app

追加方式写文件

ios::trunc

如果文件存在先删除,再创建

ios::binary

二进制方式

注意:文件的打开方式可以配合使用,用 | (或)操作符

例如:用二进制方式写文件

ios::binary | ios::out


示例:

#include<fstream> //1、包含头文件 void wfile() { //2、流对象 ofstream ofs; //3、打开文件 ofs.open("test.txt",ios::out); //4、写文件 ofs << "大鹏一日同风起" << endl; ofs << "扶摇直上九万里" << endl; ofs << "有约不来过夜半" << endl; ofs << "闲敲棋子落灯花" << endl; //关闭文件 ofs.close(); } int main() { wfile(); system("pause"); return 0; }

读文件:

步骤:

1、包含头文件:#include<fstream>

2、创建流对象:ifstream ifs;

3、打开文件并判断是否成功打开:ifs.open("文件路径",打开方式);

4、读数据:四种方式读取

5、关闭文件:ifs.close();


四种读取方式:

不推荐使用第四种,其余的可根据个人喜好使用,个人推荐第三种~

示例:

#include<fstream> //1、包含头文件 #include <string> void rfile() { //2、流对象 ifstream ifs; //3、打开文件,判断是否成功 ifs.open("test.txt", ios::in); //判断 if (! ifs.is_open()) { cout << "文件打开失败" << endl; return; } //4、写文件 ////第一种 //char Buf[1024] = { 0 }; //while (ifs>>Buf) { // cout << Buf << endl; //} ////第二种 //char Buf[1024] = { 0 }; //while (ifs.getline(Buf,sizeof(Buf))) { // cout << Buf << endl; //} //第三种 string Buf; while (getline(ifs,Buf)) { cout << Buf << endl; } ////第四种 //char c; //while ((c = ifs.get()) != EOF) { // cout << c; //} //关闭文件 ifs.close(); } int main() { rfile(); system("pause"); return 0; }

2、二进制文件

打开方式指定为:ios::binary

写文件:

二进制方式写文件主要利用流对象调用成员函数write

函数原型 :ostream& write(const char * buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

示例:

#include<fstream> //1、包含头文件 #include <string> //二进制文件 class Person { public: char Name[10]; //姓名 int age;//年龄 }; //写二进制文件 void binWfile() { Person p = { "张三",18}; //1、定义头文件 //2、流对象 ofstream ofs("Person.txt", ios::out | ios::binary); //3、打开文件 //ofs.open("Person.txt", ios::in | ios::binary); //4、写文件 ofs.write((const char*) & p,sizeof(Person)); //5、关闭文件 ofs.close(); } int main() { binWfile(); }

读文件:

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数


示例:

#include<fstream> //1、包含头文件 //二进制文件 class Person { public: char Name[10]; //姓名 int age;//年龄 }; //读二进制文件 void binRfile() { Person p; //1、定义头文件 //2、流对象 ifstream ifs("Person.txt", ios::in | ios::binary); if (!ifs.is_open()) { cout << "二进制文件打开失败" << endl; return; } //3、打开文件 //ofs.open("Person.txt", ios::in | ios::binary); //4、读文件 ifs.read((char*)&p, sizeof(Person)); //ofs.write((const char*) & name, sizeof(Person)); cout << "姓名:" << p.Name << " 年龄:" << p.age << endl; //5、关闭文件 ifs.close(); } int main() { binRfile(); }

本文共计5913个文字,预计阅读时间需要24分钟。

C语言核心编程中,如何通过类和对象实现长尾词的?

一、运算符重载+运算符重载:对已有的运算符重新定义,赋予其新的功能,以适应不同数据类型的数据运算。

二、加号+运算符重载+运算符重载:实现两个自定义数据类型对象的相加操作。

三、成员函数重载成员函数重载:在同一个类中,允许存在多个同名函数,但它们的参数列表(参数个数或参数类型)不同。

四、运算符重载+运算符重载:实现两个自定义数据类型对象的相加运算。

一、运算符重载

运算符重载:对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型


1、加号运算符重载

作用:实现两个自定义数据类型相加的运算


方法1、成员函数重载

class Person { public: Person operator+(Person& p) { Person temp; temp.m_a = this->m_a + p.m_a; temp.m_b = this->m_b + p.m_b; return temp; } int m_a; int m_b; }; void test() { Person p1; p1.m_a = 10; p1.m_b = 20; Person p2; p2.m_a = 10; p2.m_b = 20; Person p3 = p1 + p2; //等价于p1.operator+(p2) cout << "p3.m_a = " << p3.m_a << endl; cout << "p3.m_b = " << p3.m_b << endl; } int main() { test(); system("pause"); return 0; }

方法2、全局函数重载

//定义类 class Person { public: int m_a; int m_b; }; //全局函数重载 Person operator+(Person& p1,Person& p2) { Person temp; temp.m_a = p1.m_a + p2.m_a; temp.m_b = p1.m_b + p2.m_b; return temp; } void test() { Person p1; p1.m_a = 10; p1.m_b = 20; Person p2; p2.m_a = 10; p2.m_b = 20; Person p3 = p1 + p2; //等价于p1.operator+(p2) cout << "p3.m_a = " << p3.m_a << endl; cout << "p3.m_b = " << p3.m_b << endl; } int main() { test(); system("pause"); return 0; }

总结:

1、成员函数重载本质调用:Person p3 = p1.operator+(p2)

2、全局函数重载本质调用:Person p3 = operator(p1,p2)

3、运算符重载,也可以发生函数重载


2、左移运算符重载

作用:可以输出自定义数据类型

所以, 只能利用全局函数重载左移运算符


//左移重载 class Person { public: int m_a; int m_b; }; //全局函数重载 ostream& operator<<(ostream& cout, Person& p) { cout <<"p.m_a = "<<p.m_a <<"p.m_a = "<<p.m_b << endl;; return cout; } //测试 void test() { Person p1; p1.m_a = 10; p1.m_b = 20; cout << p1 << endl; } int main() { test(); system("pause"); return 0; }

扩展:如果我们需要打印的成员属性是私有的呢?全局函数无法访问,怎么重载呢?

答:对全局函数使用友元

//左移重载 class Person { public: //声明友元 friend ostream& operator<<(ostream& cout, Person& p); //构造函数赋初值 Person(int a,int b) { m_a = a; m_b = b; } private: int m_a; int m_b; }; //全局函数重载 ostream& operator<<(ostream& cout, Person& p) { cout <<"p.m_a = "<<p.m_a <<" p.m_a = "<<p.m_b << endl;; return cout; } //测试 void test() { Person p1(10,20);//调用有参构造函数 cout << p1 << endl; } int main() { test(); system("pause"); return 0; }

3、递增运算符重载

作用:通过重载,实现自己的数据类型递增


前置++:

//++运算符重载 class Person { public: //前置++ Person& operator++() { ++m_a; return *this; } int m_a = 0; }; ostream& operator<<(ostream& cout,Person p) { cout << p.m_a << endl; return cout; } //测试 void test01() { Person p; p.m_a = 100; cout << ++p << endl; //前置++ } int main() { test01(); system("pause"); return 0; }


后置++:

重载后置递增,需要用int代表占位参数,用于区分前置和后置递增

//++运算符重载 class Person { public: //后置++ Person operator++(int) { Person temp = *this; //记录操作前的结果 m_a++; return temp; } int m_a = 0; }; ostream& operator<<(ostream& cout,Person p) { cout << p.m_a << endl; return cout; } //测试 void test02() { Person p1; p1.m_a = 100; cout << "后置++:" << p1++ << endl;//后置++ cout << "后置++:" << p1 << endl; } int main() { test02(); system("pause"); return 0; }

总结:

1、前置++,返回对象自身(*this),且是用引用的方式返回

2、后置++,需要记录操作前的结果,返回记录的结果,并且是用值的方式返回(因为用于记录操作前的结果是一个临时变量,返回引用会出错)

3、后置++,在参数上需要用int 作为占位参数


4、赋值运算符重载

c++编译器至少给一个类添加4个函数

1、默认构造函数(无参,函数体为空)

2、默认析构函数(无参,函数体为空)

3、默认拷贝构造函数,对属性进行值拷贝

4、赋值运算符 operator=, 对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题


示例:

class Person { public: Person(int age) { m_a = new int(age); } ~Person() { if (m_a != NULL) { delete m_a; m_a = NULL; } } Person& operator=(Person& p) { //系统默认浅拷贝 //this->m_a = p.m_a; //深拷贝 if (this->m_a = NULL) { delete m_a; m_a = NULL; } this->m_a = new int(*p.m_a); //返回自身 return *this; } int* m_a = NULL; }; //测试 void test01() { Person p(10); Person p2(20); Person p3(30); cout << "赋值前:p1:" << *p.m_a << " p2:" << *p2.m_a << " p3:" << *p3.m_a << endl; p = p2 = p3; cout << "赋值后:p1:" << *p.m_a << " p2:" << *p2.m_a << " p3:" << *p3.m_a << endl; } int main() { test01(); system("pause"); return 0; }

5、关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行比较


示例:

//关系运算符重载 //1、== 2、 != class Person { public: Person(string name,int age) { m_Name = name; m_Age = age; } //重载 == int operator==(Person& p){ if (this->m_Age == p.m_Age && this->m_Name == p.m_Name) { return 1; } else return 0; } //重载 != int operator!=(Person& p){ if (this->m_Age != p.m_Age || this->m_Name != p.m_Name) { return 1; } else return 0; } string m_Name; int m_Age; }; //测试 void test01() { Person p("Tom",18); Person p2("Tom", 18); Person p3("Tom", 18); Person p4("Jiu", 18); if (p == p2) { cout << "p 和 p2 相等!" << endl; } else cout << "p 和 p2 不相等!" << endl; if (p3 != p4) { cout << "p3 和 p4 不相等!" << endl; } else cout << "p3 和 p4 相等!" << endl; } int main() { test01(); system("pause"); return 0; }

6、函数调用运算符重载

函数调用运算符 () 也可以重载

由于重载后使用的方式非常像函数的调用,因此称为仿函数

仿函数没有固定写法,非常灵活

示例:

//函数调用运算符重载 //打印类 class myPrint { public: void operator()(string name) { m_Name = name; cout << m_Name << endl; } string m_Name; }; void test() { myPrint mp; mp("Hello World"); } int main() { test(); system("pause"); return 0; }

二、继承

继承是面向对象的三大特征之一

在定义一些类的时候,如果下级别的成员除了拥有上一级的共性,还有自己的特性,这时候我们就可以选择继承,来减少重复代码


1、继承的基本语法

class 子类 :继承类型 父类

例如:

class son : public father{ //成员 };

注意事项:

1、子类又称:派生类,父类又称:基类

2、子类的成员包括从父类继承来的,以及自己增加的成员

3、继承过来的表现共性,增加的表现个性


2、继承方式

继承方式分三种:

1、公有继承(public):当一个子类公有继承父类时,父类的公有成员也是子类的公有成员,父类的保护成员也是子类的保护成员,父类的私有成员不能直接被子类访问,但是可以通过调用父类的公有和保护成员来访问。

2、保护继承(protected): 当一个子类保护继承父类时,父类的公有和保护成员将成为子类的保护成员。

3、私有继承(private):当一个子类私有继承父类时,父类的公有和保护成员将成为子类的私有成员。

3、继承中的对象模型

问题:从父类继承过来的成员,那些属于子类对象?

答:在父类中的非静态成员属性都会被子类继承,私有属性也会继承,只是无法访问。会被编译器隐藏。


扩展:利用开发人员命令提示工具查看对象模型

1、跳转盘符:E:(存放文件的盘符)

2、跳转文件路径: cd 具体路径下

3、查看命令:cl /d1 reportSinglcleClassLayout类名 "文件名"

4、继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数


问题:父类和子类的构造和析构顺序谁先谁后?

答:

1、先构造父类,在构造子类

2、析构与构造相反



可以看见,子类父类的顺序,同,一个类中的类成员顺序是一致的

5、继承同名成员处理方式

问题:当子类和父类出现同名的成员,如何通过子类或父类中同名的数据?

答:访问子类同名成员,直接访问;访问父类同名成员,需要加作用域。


注意事项:

1、如果子类中出现了和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,即,不可直接调用重载

解决方法:调用时,增加作用域即可

6、继承同名静态成员处理

静态成员和非静态成员出现同名时,处理方法一致

1、访问子类同名成员,直接访问

2、访问父类同名成员,需要加作用域

7、多继承语法

c++中允许一个类继承多个类

语法:class 子类 : 继承方式 父类1,继承方式 父类2...


多继承可能回引发父类中同名成员出现,需要加作用域区分,实际开发中,不建议使用

//多父类继承 class Base1 { //父类1 public: Base1() { m_b1 = 100; } int m_b1; }; class Base2 { //父类2 public: Base2() { m_b2 = 200; } int m_b2; }; class Base3 { //父类3 public: Base3() { m_b3 = 300; } int m_b3; }; //子类多继承 class Son : public Base1,public Base2, public Base3{ public: int m_s1 = 400; }; void test() { Son s; cout << "多继承子类字节数:" << sizeof(s) << endl; cout << "Base1 = " << s.Base1::m_b1 << endl;; cout << "Base2 = " << s.Base2::m_b2 << endl;; cout << "Base3 = " << s.Base3::m_b3 << endl;; cout << "Son = " << s.m_s1 << endl;; } int main() { test(); system("pause"); return 0; }

8、菱形继承

概念:

1、两个子类继承同一个父类

2、又有某个类同时继承两个子类

3、这种继承被称为菱形继承,或砖石继承


菱形继承的问题:

1、羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性。

答:加以作用域区分!

2、羊驼继承自动物的数据继承了两份,导致资源浪费

答:利用虚继承(在继承之前加上关键字virtual,变为虚继承),可以解决!

//菱形继承 class BASE { //父父类 public: int m_a; }; //父类1 class Base1 :virtual public BASE {}; //父类2 class Base2 :virtual public BASE {}; //子类 class Son : public Base1, public Base2{}; void test() { Son s; s.Base1::m_a = 100; s.Base2::m_a = 200; cout << "父类1,base1 :" << s.Base1::m_a << endl; cout << "父类2,base3 :" << s.Base2::m_a << endl; cout << "子类:Son :" << s.m_a << endl; } int main() { test(); system("pause"); return 0; }

虚继承原理:子类继承父类的两个指针,指针通过偏移量,找到同一份数据。

三、多态

1、多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类:

1、静态多态: 函数重载 和 运算符重载 属于静态多态,复用函数名

2、动态多态: 子类和虚函数实现运行时多态

区别:

1、静态多态的函数地址早绑定 - 编译阶段确定函数地址

2、动态多态的函数地址晚绑定 - 运行阶段确定函数地址

示例:

//多态 //动物类 class Animal { public: //虚函数,用virtual关键字修饰的函数 virtual void speek() { cout << "动物说话..." << endl; } }; //猫类 class Cat : public Animal { void speek() { cout << "猫在说话..." << endl; } }; //狗类 class Dog : public Animal { void speek() { cout << "狗在说话..." << endl; } }; void doSpeek(Animal& ani) { //父类和子类类型可以相互转换 ani.speek(); } void test() { Cat c; Dog d; //调用执行说话函数 doSpeek(c); doSpeek(d); } int main() { test(); system("pause"); return 0; }


总结:

动态多态的满足条件:

1、有继承关系

2、子类重写父类的虚函数


区分重写和重载:

重写:函数返回值类型,函数名,参数列表完全相同

重载:函数的参数的数量,类型或位置不同

动态多态的使用:

父类的指针或引用指向子类对象

2、深入剖析多态原理:

Animal类内部结构:

Cat类内部结构:

1、未重写父函数

2、重写父函数后

当子类重写父类虚函数后,子类的虚函数表内部会替换成子类的虚函数地址,父类不发生改变。

3、纯虚函数和抽象类

纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;

但类中有了纯虚函数,这个类就被称为抽象类


抽象类特点:

1、无法实例化对象

2、子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例:

4、虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码


解决方式:将父类中的析构函数改为虚析构或者纯虚析构


虚析构和纯虚析构共性:

1、可以解决父类指针释放子类对象

2、都需要有具体的函数实现


虚析构和纯虚析构区别:

如果是纯虚析构,该类属于抽象类,无法实例化对象


虚析构语法:

virtual ~类名(){}

纯虚析构语法:

virtual ~类名() = 0;

C语言核心编程中,如何通过类和对象实现长尾词的?

类名::~类名(){}


示例:

//虚析构和纯虚析构 class Base { public: Base() { cout << "Base构造函数调用" << endl; } virtual ~Base() = 0; virtual void func() = 0; }; Base:: ~Base(){ cout << "Base析构函数调用" << endl; } class Son :public Base { public: Son(string name) { m_name = new string(name); cout << "son构造函数调用" << endl; } ~Son() { cout << "son析构函数调用" << endl; if (m_name != NULL) { delete m_name; m_name = NULL; } } void func() { cout << *m_name << endl; } string* m_name; }; void test01() { Base* b = new Son("TOM"); b->func(); delete b; } int main() { test01(); system("pause"); return 0; }

使用虚析构或纯虚析构前:

使用虚析构或纯虚析构后:

四、文件操作

文件操作头文件:<fstream>

操作文件的三大流:

1、ofstream:写操作

2、ifstream:读操作

3、fstream:读写操作


1、文本文件

写文件

步骤:

1、包含头文件:#include<fstream>

2、创建流对象:ofstream ofs;

3、打开文件:ofs.open("文件路径",打开方式);

4、写数据:ofs<<"写入的数据"

5、关闭文件:ofs.close();

文件打开方式:

打开方式

解释

ios::in

为读文件而打开文件

ios::out

为写文件而打开文件

ios::ate

初始位置:文件尾

ios::app

追加方式写文件

ios::trunc

如果文件存在先删除,再创建

ios::binary

二进制方式

注意:文件的打开方式可以配合使用,用 | (或)操作符

例如:用二进制方式写文件

ios::binary | ios::out


示例:

#include<fstream> //1、包含头文件 void wfile() { //2、流对象 ofstream ofs; //3、打开文件 ofs.open("test.txt",ios::out); //4、写文件 ofs << "大鹏一日同风起" << endl; ofs << "扶摇直上九万里" << endl; ofs << "有约不来过夜半" << endl; ofs << "闲敲棋子落灯花" << endl; //关闭文件 ofs.close(); } int main() { wfile(); system("pause"); return 0; }

读文件:

步骤:

1、包含头文件:#include<fstream>

2、创建流对象:ifstream ifs;

3、打开文件并判断是否成功打开:ifs.open("文件路径",打开方式);

4、读数据:四种方式读取

5、关闭文件:ifs.close();


四种读取方式:

不推荐使用第四种,其余的可根据个人喜好使用,个人推荐第三种~

示例:

#include<fstream> //1、包含头文件 #include <string> void rfile() { //2、流对象 ifstream ifs; //3、打开文件,判断是否成功 ifs.open("test.txt", ios::in); //判断 if (! ifs.is_open()) { cout << "文件打开失败" << endl; return; } //4、写文件 ////第一种 //char Buf[1024] = { 0 }; //while (ifs>>Buf) { // cout << Buf << endl; //} ////第二种 //char Buf[1024] = { 0 }; //while (ifs.getline(Buf,sizeof(Buf))) { // cout << Buf << endl; //} //第三种 string Buf; while (getline(ifs,Buf)) { cout << Buf << endl; } ////第四种 //char c; //while ((c = ifs.get()) != EOF) { // cout << c; //} //关闭文件 ifs.close(); } int main() { rfile(); system("pause"); return 0; }

2、二进制文件

打开方式指定为:ios::binary

写文件:

二进制方式写文件主要利用流对象调用成员函数write

函数原型 :ostream& write(const char * buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数

示例:

#include<fstream> //1、包含头文件 #include <string> //二进制文件 class Person { public: char Name[10]; //姓名 int age;//年龄 }; //写二进制文件 void binWfile() { Person p = { "张三",18}; //1、定义头文件 //2、流对象 ofstream ofs("Person.txt", ios::out | ios::binary); //3、打开文件 //ofs.open("Person.txt", ios::in | ios::binary); //4、写文件 ofs.write((const char*) & p,sizeof(Person)); //5、关闭文件 ofs.close(); } int main() { binWfile(); }

读文件:

二进制方式读文件主要利用流对象调用成员函数read

函数原型:istream& read(char *buffer,int len);

参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数


示例:

#include<fstream> //1、包含头文件 //二进制文件 class Person { public: char Name[10]; //姓名 int age;//年龄 }; //读二进制文件 void binRfile() { Person p; //1、定义头文件 //2、流对象 ifstream ifs("Person.txt", ios::in | ios::binary); if (!ifs.is_open()) { cout << "二进制文件打开失败" << endl; return; } //3、打开文件 //ofs.open("Person.txt", ios::in | ios::binary); //4、读文件 ifs.read((char*)&p, sizeof(Person)); //ofs.write((const char*) & name, sizeof(Person)); cout << "姓名:" << p.Name << " 年龄:" << p.age << endl; //5、关闭文件 ifs.close(); } int main() { binRfile(); }