(九)羽夏看C语言——C++番外篇
2021-09-09 14:14
921 查看
写在前面
此系列是本人一个字一个字码出来的,包括示例和实验截图。本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我。
你如果是从中间插过来看的,请仔细阅读**(一)羽夏看C语言——简述** ,方便学习本教程。本篇是C++番外篇,会将零碎的东西重新集合起来介绍,可能会与前面有些重复或重合。
☀️ 封装
将函数定义到结构体内部,就是封装。
☀️ 类
带有函数的结构体,称为类。
☀️ 成员函数
结构体里面的函数,称为成员函数。
☀️ 结构体传参
1️⃣ 直接使用结构体传参
#include <iostream> using namespace std; struct my_struct { int a; int b; int c; }; int mplus(my_struct &struct_) { return struct_.a + struct_.b + struct_.c; } int main() { my_struct struct_ = { 1,2,3 }; int res = mplus(struct_); printf_s("%d", res); system("pause"); return 0; }
my_struct struct_ = { 1,2,3 }; mov dword ptr [struct_],1 mov dword ptr [ebp-10h],2 mov dword ptr [ebp-0Ch],3 int res = mplus(struct_); sub esp,0Ch mov eax,esp mov ecx,dword ptr [struct_] mov dword ptr [eax],ecx mov edx,dword ptr [ebp-10h] mov dword ptr [eax+4],edx mov ecx,dword ptr [ebp-0Ch] mov dword ptr [eax+8],ecx call mplus (0C912C0h) add esp,0Ch mov dword ptr [res],eax
2️⃣ 使用结构体指针/引用传参
#include <iostream> using namespace std; struct my_struct { int a; int b; int c; }; int mplus(my_struct& struct_) { return struct_.a + struct_.b + struct_.c; } int main() { my_struct struct_ = { 1,2,3 }; int res = mplus(struct_); printf_s("%d", res); system("pause"); return 0; }
my_struct struct_ = { 1,2,3 }; mov dword ptr [struct_],1 mov dword ptr [ebp-10h],2 mov dword ptr [ebp-0Ch],3 int res = mplus(struct_); lea eax,[struct_] push eax call mplus (01A12C0h) add esp,4 mov dword ptr [res],eax
故尽量使用结构体指针传参
3️⃣ 封装使用
#include <iostream> using namespace std; struct my_struct { int a; int b; int c; int mplus() { return a + b + c; } }; int main() { my_struct struct_ = { 1,2,3 }; int res = struct_.mplus(); printf_s("%d", res); system("pause"); return 0; }
- 生成的反汇编同 2️⃣
- 函数并不属于这个结构体,这样做仅仅是为了使用方便,但虚函数会多占用4个字节(无论多少个)。
☀️ this指针
struct my_struct { int a; int b; int c; void init(int a,int b,int c) { this -> a = a; this -> b = b; this -> c = c; } int mplus() { return a + b + c; } };
my_struct struct_ ; struct_.init(1, 2, 3); push 3 push 2 push 1 lea ecx,[struct_] //传地址到ecx,即this指针 call my_struct::init (05A12C0h) int res = struct_.mplus(); lea ecx,[struct_] //传地址到ecx,即this指针 call my_struct::mplus (05A1320h) mov dword ptr [res],eax
void init(int a,int b,int c) { push ebp mov ebp,esp sub esp,0CCh push ebx push esi push edi push ecx lea edi,[ebp-0CCh] mov ecx,33h mov eax,0CCCCCCCCh rep stos dword ptr es:[edi] pop ecx mov dword ptr [this],ecx //this指针 this->a = a; mov eax,dword ptr [this] mov ecx,dword ptr [a] mov dword ptr [eax],ecx this->b = b; mov eax,dword ptr [this] mov ecx,dword ptr [b] mov dword ptr [eax+4],ecx this->c = c; mov eax,dword ptr [this] mov ecx,dword ptr [c] mov dword ptr [eax+8],ecx } pop edi pop esi pop ebx mov esp,ebp pop ebp ret 0Ch
- this指针是编译器默认传入的,通常都会使用ecx进行参数的传递。
- 成员函数都有this指针,无论是否使用。
- this指针不能做
++
和--
等运算,不能重新被赋值。 - this指针不占用结构体的宽度。
☀️ 构造函数
- 与类同名且没有返回值
- 创建对象的时候执行/主要用于初始化
- 可以有多个(最好有一个无参的),称为重载其他函数也可以重载
- 编译器不要求必须提供
☀️ 析构函数
- 只能有一个析构函数,不能重载
- 不能带任何参数
- 不能带返回值
- 主要用于清理工作
- 编译器不要求必须提供
☀️ 在堆中创建对象
new=
malloc+
构造函数
delete=
free+
析构函数
int* p = new int; delete p;
int* p = new int[3]; delete[] p;
☀️ 引用
int main() { int x = 2; int& ref = x; //定义引用类型 ref = 3; printf("%d",ref); //输出为3 return 0; }
- 引用必须赋初始值,且只能指向一个变量,“从一而终”。
- 对引用赋值,是对其指向的变量赋值,而并不是修改引用本身的值。
- 对引用做运算,就是对其指向的变量做运算,而不是对引用本身做运算。
- 引用类型就是一个“弱化了的指针”。
☀️ 常引用
void show(const int& content) //函数内部无法修改content的值 { content = 5; //编译器检查,编译不通过 printf("%d",content); }
☀️ 面向对象程序设计之继承与封装
class Person { int age; char sex; public: Person(int age,char sex) { setAge(age); setSex(sex); } void setAge(int age) { age < 0 ? age = 0 : this->age = age; } void setSex(char sex) { this->sex = sex == 'b' || sex == 'g' ? sex : 'b'; } }; class Teacher:public Person //注意不能少了public { int grade; public: Teacher(int age,char sex,int grade):Person(age,sex) { setAge(age); setSex(sex); SetGrade(grade); } void SetGrade(int grade) { this ->grade = grade < 0 ? 0 : grade; } };
➡️ 分析 1️⃣
setAge/
setSex/
SetGrade系列函数的设计是为了输入的数据更加可控,封装性更好。 2️⃣
Teacher(int age,char sex,int grade):Person(age,sex)中若没有
:Person(age,sex),则默认调用
Person()进行构造。如果
Person没有这个函数,编译器就会报错。
☀️ 面向对象程序设计之多态
class Person { int age; char sex; public: Person(int age,char sex) { setAge(age); setSex(sex); } void setAge(int age) { age < 0 ? age = 0 : this->age = age; } void setSex(char sex) { this->sex = sex == 'b' || sex == 'g' ? sex : 'b'; } virtual void show() //利用虚函数实现多态 { printf("%d %d ",age,sex); } }; class Teacher:public Person { int grade; public: Teacher(int age,char sex,int grade):Person(age,sex) { setAge(age); setSex(sex); SetGrade(grade); } void SetGrade(int grade) { this ->grade = grade < 0 ? 0 : grade; } void show() //重写实现,共用接口 { Person::show(); printf("%d",grade); } }; //调用此函数,就能实现打印Person或Teacher里的数据 void Print(Person& per) { per.show(); }
☀️ 纯虚函数
- 虚函数目的是提供一个统一的接口,被继承的子类重载,以多态的形式被调用。
- 如果基类中的函数没有任何实现的意义,那么可以定义成纯虚函数:
virtual 返回类型 函数名(参数列表) = 0;
- 含有纯虚函数的类被称为抽象类,不能创建对象。
- 虚函数可以被直接使用,也可以被子类重载以后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用。
☀️ 虚表
- 以下是实现代码
#include <iostream> #include <Windows.h> using namespace std; class Person { int age; char sex; public: Person(int age, char sex) { setAge(age); setSex(sex); } void setAge(int age) { age < 0 ? age = 0 : this->age = age; } void setSex(char sex) { this->sex = sex == 'b' || sex == 'g' ? sex : 'b'; } virtual void show() //利用虚函数实现多态 { printf("%d %d ", age, sex); } virtual void whoami() { puts("Person"); } }; class Teacher :public Person { int grade; public: Teacher(int age, char sex, int grade) :Person(age, sex) { setAge(age); setSex(sex); SetGrade(grade); } void SetGrade(int grade) { this->grade = grade < 0 ? 0 : grade; } void show() //重写实现,共用接口 { Person::show(); printf("%d", grade); } void whoami() { puts("Teacher"); } }; void Print(Person& per) { per.show(); } void whoami(Person& per) { per.whoami(); } int main() { Teacher t(20, 'b', 20); Print(t); whoami(t); system("pause"); return 0; }
- 通过下断点,我们发现
t
变量不同之处:
在
t的
Person里多了一个成员
__vfptr,这个指向 虚表 的指针,我们看一下内存布局。
经过检验,里面的值是函数地址,第一个是指向
Teacher里面的
show函数的地址,第二个是指向
Teacher里面的
whoami的地址,这就是所谓的虚表。看编译器如何利用虚表实现多态,看下面的反汇编:
void whoami(Person& per) { push ebp mov ebp,esp sub esp,0C0h push ebx push esi push edi lea edi,[ebp-0C0h] mov ecx,30h mov eax,0CCCCCCCCh rep stos dword ptr es:[edi] per.whoami(); mov eax,dword ptr [per] mov edx,dword ptr [eax] mov esi,esp //获取per地址的第一个成员,即为__vfptr。 mov ecx,dword ptr [per] //由于whoami在虚表的第二个位置,故需要edx+4才是它的地址 mov eax,dword ptr [edx+4] call eax
☀️ 模板
- 示例
template<c1ass T> void Sort(T* arr,int nLength) { int i; int k; for(i=0;i<nLength-1;i++) { for(k=0;k<nLength-1-i;k++) { if(arr[k]>arr[k+1]) { T temp =arr[k]; arr[k]= arr[k+1]; arr[k+1]=temp; } } } }
- 本质
编译器帮我们生成函数,
T有多少种,编译器就生成多少个此函数。
☀️ 抽象类
作用:作为标准规范,方便对子类管理
- 含有纯虚函数的类,称为抽象类(Abstract Class)
- 抽象类也可以包含普通的函数
- 抽象类不能实例化
☀️ 拷贝构造函数
拷贝构造函数由编译器提供,不需编写,他会把原对象数据原封不动的复制到目标对象,称之为浅拷贝。
#include <iostream> #include <Windows.h> using namespace std; class Person { int age; char sex; public: Person(int age, char sex) { setAge(age); setSex(sex); } void setAge(int age) { age < 0 ? age = 0 : this->age = age; } void setSex(char sex) { this->sex = sex == 'b' || sex == 'g' ? sex : 'b'; } void show() { printf("%d %d ", age, sex); } }; int main() { Person p0(20,'b'); Person p(p0); p.show(); }
- 如果类里面有指针,且赋值的数据是内容不是简单的地址,需要自己重写。
class Person { int age; char sex; public: Person(int age, char sex) { setAge(age); setSex(sex); } }; class Teacher :public Person { int grade; public: Teacher(int age, char sex, int grade) :Person(age, sex) { setAge(age); setSex(sex); SetGrade(grade); } void SetGrade(int grade) { this->grade = grade < 0 ? 0 : grade; } /*下面是拷贝构造函数*/ Teacher(const Teacher& t):Person(t) { //除了父类全部由自己实现 } /*Person(t)如果没有,父类的拷贝构造需要自己实现*/ };
☀️ 赋值重载
- 如下是示例
CBase& operator=(const CBase& ref) { m_nLength = ref.m_nLength; if(m_pBuFfer != NULL) delete[] m_pBuffer; m_pBuffer = new char[m_nLength]; memcpy(m_pBufFer,ref.m_pBuffer,m_nLength); return *this; }
CSub& operator= ( const CSub& ref) { CBase::operator= (ref); m_nIndex = ref.m_nIndex; return *this; }
- 为什么拷贝构造函数不能这样写? 子类能全盘继承父类的在何东西,除了构造函数和析构函数,所以不能在函数体中显式调用父类的拷贝构造。
☀️ 友元
友元破坏了C++面向对象的封装特性,不推荐使用。
class CObject { friend void Print0bject(cobject* pObject); //告诉编译器Print0bject函数可以访问我的私有成员 private: int x; public: CObject(){} CObject(int x) { this -> x = x; } }; void Printobject(cobject* pObject) { printf("%d \n",pObject->x); }
TestFriend类中的函数都可以直接访问MyObject中的私有成员,但只是单向的。
class MyObject { friend class TestFriend; private: int x; public: My0bject(){} MyObject(int x) { this -> x =x; } }; class TestFriend { public: void Fn(My0bject* pObject) { printf("%d tn", pObject->x); }
☀️ 内部类
内部类和外部类之间的私有成员无法互通,如果一个类只在模块内部使用,则可以实现类名隐藏。
☀️ 命名空间(namespace)
运用命名空间可以解决命名冲突问题
- 所有没有明确命名的命名空间都在全局命名空间
#include <iostream> using namespace x; int Test() { return 0; } int main() { ::Test(); //如果x命名空间也有Test函数,可用此方式 system("pause"); return 0; }
☀️ static
关键字
将变量和函数私有的全局化,声明在类里不属于此类的成员。
class CBase { public: CBase(int x,int y); static int GetSum(); //声明静态成员函数 private: int x,y; static int Sum; //声明静态数据成员 } int CBase::Sum = 10; //定义并初始化静态数据成员
☀️ 面向对象设计中的static
之静态数据成员:
1️⃣ 静态数据成员存储在全局数据区,且必须初始化 2️⃣ 静态数据成员和普通数据成员一样遵从
public,
protected,
private访问规则 3️⃣ 类的静态数据成员有两种访问形式:
类对象名.静态数据成员名、
类类型名::静态数据成员名4️⃣ 同全局变量相比,使用静态数据成员可以
避免命名冲突和
实现信息隐藏5️⃣ 出现在类体外的函数定义不能指定关键字
static6️⃣ 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数 7️⃣ 非静态成员函数可以任意地访问静态成员函数和静态数据成员 8️⃣ 静态成员函数不能访问非静态成员函数和非静态数据成员
☀️ static
实现单实例模式
class CSingleton { public: static CSingieton* GetInstance() { if(m_pInstance == NULL) m_pInstance = new CSingleton(); return m_pInstance; } private: CSingleton(){} static CSingleton* m_pinstance; //定义静态成员 }; CSingleton* CSingleton::m_pInstance = NULL;//初始化静态成员 int main(int argc, char* agrv[]) { CSingleton* p1= CSingleton::GetInstance(); CSingleton* p2= CSingleton::GetInstance(); //p1和p2的值是一样的 return 0; }
☀️ C++碎碎念
- 继承:继承就是数据的复制,减少重复代码的编写
- 继承不仅仅局限于父类,它会把父类继承到的东西全部拿来
- 如果子类(记为A)有和父类(记为B)相同的成员,以子类为准,如果非要使用父类的重名成员(记为C),则通过
A.B::C
引用 struct
和class
里的私有成员不是绝对不能访问的,只是不能直接引用,需要用指针获取。class
与struct
的区别: 编译器默认class
中的成员为private
,而struct
中的成员为public
。继承也是如此,class
继承注意在继承符号面的public
不要漏下。- 父类的指针可以指向子类的对象
- 操作符重载(在类里面,只是方便写代码而设计)
- 面向过程设计中的
static
:“私有”的全局变量
相关文章推荐
- C语言、C++中的union用法总结
- 【C语言】C\C++ 文件打开方式
- C++ c++与C语言的区别(struct类型的加强,函数-变量类型加强,bool类型)
- 0930_C/C++笔试题_12:16道c语言面试【6/7】
- C语言和C++中部分名词函数记录。
- linux下c程序c++程序混合编译,c程序中调用c++程序,c语言项目与c++语言项目的合并
- C++与C语言的区别——C++是对C语言的扩展(二)——输入cin与输出cout
- C语言/C++中怎样产生随机数
- C语言,C++练习题
- C语言与C++不得不说的那点事
- Ubuntu下安装C/C++开发环境【!!!有更新!!!Ubuntu14.10下使用eclipse搭建C语言开发环境】
- C语言下extern static用法一结,及C++下extern “C”的用法
- C语言/C++如何生成随机数
- 有C++、Java、C#,为什么还要学C语言?
- 华为2014年机试题【字符串压缩】-【C语言/C++】
- c++与c语言的结构体实例化问题
- [C/C++基础] C语言常用函数memset的使用方法
- 在QQ群里有人提问有没有C语言的XML解析,偶然想到了这个问题:C++调用C库,简单试验:
- 《对C语言编写的银行系统用C++进行重构——4》
- 详解C++中的const关键字及与C语言中const的区别