写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。本人非计算机专业,可能对本教程涉及的事物没有了解的足够深入,如有错误,欢迎批评指正。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读(一)羽夏看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
  1. this指针是编译器默认传入的,通常都会使用ecx进行参数的传递。
  2. 成员函数都有this指针,无论是否使用。
  3. this指针不能做++--等运算,不能重新被赋值。
  4. this指针不占用结构体的宽度。

️ 构造函数

  1. 与类同名且没有返回值
  2. 创建对象的时候执行/主要用于初始化
  3. 可以有多个(最好有一个无参的),称为重载其他函数也可以重载
  4. 编译器不要求必须提供

️ 析构函数

  1. 只能有一个析构函数,不能重载
  2. 不能带任何参数
  3. 不能带返回值
  4. 主要用于清理工作
  5. 编译器不要求必须提供

️ 在堆中创建对象

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;
}
  1. 引用必须赋初始值,且只能指向一个变量,“从一而终”。
  2. 对引用赋值,是对其指向的变量赋值,而并不是修改引用本身的值。
  3. 对引用做运算,就是对其指向的变量做运算,而不是对引用本身做运算。
  4. 引用类型就是一个“弱化了的指针”。

️ 常引用

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();
}

️ 纯虚函数

  1. 虚函数目的是提供一个统一的接口,被继承的子类重载,以多态的形式被调用。
  2. 如果基类中的函数没有任何实现的意义,那么可以定义成纯虚函数:

    virtual 返回类型 函数名(参数列表) = 0;
  3. 含有纯虚函数的类被称为抽象类,不能创建对象。
  4. 虚函数可以被直接使用,也可以被子类重载以后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用。

️ 虚表

  • 以下是实现代码
#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变量不同之处:

  在tPerson里多了一个成员__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有多少种,编译器就生成多少个此函数。

️ 抽象类

作用:作为标准规范,方便对子类管理

  1. 含有纯虚函数的类,称为抽象类(Abstract Class)
  2. 抽象类也可以包含普通的函数
  3. 抽象类不能实例化

️ 拷贝构造函数

  拷贝构造函数由编译器提供,不需编写,他会把原对象数据原封不动的复制到目标对象,称之为浅拷贝。

#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)

运用命名空间可以解决命名冲突问题

  1. 所有没有明确命名的命名空间都在全局命名空间
#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️⃣ 出现在类体外的函数定义不能指定关键字static

6️⃣ 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数

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++碎碎念

  1. 继承:继承就是数据的复制,减少重复代码的编写
  2. 继承不仅仅局限于父类,它会把父类继承到的东西全部拿来
  3. 如果子类(记为A)有和父类(记为B)相同的成员,以子类为准,如果非要使用父类的重名成员(记为C),则通过A.B::C引用
  4. structclass里的私有成员不是绝对不能访问的,只是不能直接引用,需要用指针获取。
  5. classstruct的区别:

      编译器默认class中的成员为private,而struct中的成员为public。继承也是如此,class继承注意在继承符号面的public不要漏下。
  6. 父类的指针可以指向子类的对象
  7. 操作符重载(在类里面,只是方便写代码而设计)
  8. 面向过程设计中的static:“私有”的全局变量

最新文章

  1. html+css图片下弹出蒙版
  2. ABP的数据过滤器(Data Filters)
  3. High Performance Animations
  4. CodeForces 670D2 Magic Powder 二分
  5. CSS透明属性详解代码
  6. 浅析WCF与WebService、WPF与Silverlight 区别
  7. (搬运工)国内顺利使用Google的另类技巧
  8. Maven 学习笔记
  9. How to Install/Deinstall Oracle Workspace Manager (文档 ID 263428.1)
  10. ssh 依赖关系
  11. UITableView 之 取消选中
  12. Flask博客开发——Tinymce编辑器
  13. vue computed、methods、watch的区别
  14. 消息队列MQ】各类MQ比较
  15. day22 Pythonpython 本文json模块
  16. Vijos1906 联合权值 NOIP2014Day1T2 树形动态规划
  17. Response实例
  18. 使用Maven构建项目
  19. JSON自动生成相关类
  20. JSmpeg-用JavaScript编写的视频播放器

热门文章

  1. 理清 UT、UTC、GMT和CST
  2. xampp搭建开源项目iwebshop后,服务器重启后再启动xampp显示组件都启动ok,但是实际启动失败解决办法
  3. Sunset靶机
  4. 项目去O实践
  5. k8s入门你至少需要会哪些
  6. SpringBoot数据访问之整合Mybatis配置文件
  7. 3个月零基础入门Python+数据分析,详细时间表+计划表分享
  8. 数据增广imgaug库的使用
  9. Linux线程简单介绍
  10. VLAN-4 单臂路由实现vlan之间路由