1.C++是对C的扩展,C原有的语法,C++都支持,并且在此基础加了新的语法:封装、继承等
2.为什么扩展:为了方便使用,写代码更高效,但是编译器又处理了更多的工作
3.编译器也在C的基础上帮我们做了更多的工作,写了更多的代码
4.C++也新增很多概念,除了如上所说,还有:类、成员函数、命名空间等
结构体作为参数传递的时候,本质和我们传递整数的时候是没有区别的
小结1
1.结构体可以作为参数传递
2.传递到哪了?堆栈里
3.有什么区别?它不用push传参
把函数定义在结构体内的这种写法,就叫做封装
封装的好处:函数可以非常方便的使用当前结构体内的其他成员
1、This 指针是编译器默认传入的,只要把函数定义到结构体内部,编译器都会把This 指针传入进来
2、通常编译器都会用ECX来存储This 指针
3、不管用不用,没参数没返回值,都在那
平时逆向的时候,当看到使用ECX来传递结构体的地址时,肯定就是C++开发的
编译器不允许this进行运算,也不允许对this重新赋值,因为this就是当前结构体的地址,对他运算和赋值没有意义,他就只有一个含义,当前结构体的首地址,只是为了方便使用
小结
1、This 指针是编译器默认传入的,通常使用ecx进行参数的传递
2、成员函数都有This 指针,不管用不用
3、This 指针不能参与运算,不能重新被赋值
4、This 指针不占用当前结构体宽度
构造函数
class Counter
{
public:
// 类Counter的构造函数
// 特点:以类名作为函数名,无返回类型
Counter()
{
m_value = 0;
}
private:
// 数据成员
int m_value;
}
构造函数的作用:初始化对象的数据成员。
构造函数特点:
函数名称和结构体名称一样
创建对象的时候执行其他函数还要通过对象名)
主要用于初始化,也可以达成我们在创建对象的时候实现什么功能,就可以把代码写到构造函数里
构造函数可以创建多个,想创建几个创建几个,最好有一个无参的,多个构造函数之间的关系称为:重载关系没有
返回值参数
想写几个写几个
编译器不强制要求有构造函数
析构函数
析构函数和构造函数写法相似
~Study()
{
printf("观察是否执行");
}
和构造函数不同的是,要在前面加一个波浪号
析构函数只允许有一个,不能有多个,不能重载
析构函数里不允许存在参数
主要用于清理工作
编译器不强制提供析构函数
什么是继承?
继承就是复制数据
为什么用继承?
减少重复代码编写
struct Study
{
int old;
int six;
};
struct Study1:Study //继承
{
int banji;
};
这个代码里
Study称为父类,或者基类
Study1称为子类,或者派生类,因为他继承了Study
如果重复不加提示用哪个,编译器默认认为我们使用的是子类自己的成员
多重继承
Public: 使成员对于整个程序内(类内类外)都是可以访问的
Protected: 使派生类也可以访问其数据,但是又不想让其他的函数或类进行访问
Private: 只有他所在类的成员函数可以访问
简单说,private实现的就是一种封装,让类的对象(C++primer里面叫类的用户)不能直接访问,而public让成员可以被程序的任何地方访问到。
派生类的成员或友元只能通过派生类对象来访问基类的受保护对象(即protected对象)。派生类对于一个基类对象中的受保护成员没有任何访问特权。
创建对象位置
1.全局变量区域
Study a;
2.栈
void main()
{
Study a;
}
写在函数里的时候,就不在全局变量区分配,就会分配到堆栈中
因为执行函数的时候才会创建,函数执行完毕,分配给对象的空间,就没了
3.堆
C语言有一个函数:
malloc()函数:需要用到内存,不确定大小时,用malloc()申请内存空间,malloc()函数申请的空间就在堆中
C++想把对象创建在堆中,怎么解决?
new函数:Study* a = new Study();
后面的new Study(),这个Study就不是像前面那个一样,是类名了,这里用的是构造函数这个构造函数可以用有参的,也可以用无参的。
如果选择有参数的构造函数,记得要把参数写进去,如:Study(9,9);
#include
#include
class Study
{
private:
int a;
int b;
public:
Study()// 构造函数
{
printf("Study() 执行 - \n");
}
Study(int a, int b)
{
printf("Study(Can Shu) 执行 - \n");
this->a = a;
this->b = b;
}
~Study()//析构函数
{
printf("~Study() 执行 - \n");
}
};
这样创建的对象,不在栈,也不再全局变量区,而是在堆中
在堆中申请内存,使用完了要进行释放,c语言中是用free函数释放内存
在C++中,就用delete释放:delete a;
也就是跟上创建的对象名字
new / delete函数
#include
#include
class Study
{
private:
int a;
int b;
public:
Study()
{
printf("Study() 执行 - \n");
}
Study(int a, int b)
{
printf("Study(Can Shu) 执行 - \n");
this->a = a;
this->b = b;
}
~Study()
{
printf("~Study() 执行 - \n");
}
};
int main()
{
//堆中创建对象
Study* a = new Study(2,3)//在堆中创建对象时调用析构函数,除了在堆中申请对象所需的空间外,还调用了析构函数
//释放对象
delete a;//delete释放的时候,不仅仅释放了内存,还调用了析构函数
return 0;
}
new就相当于malloc + 构造函数
数组和类数组的创建方法
数组:
//C 数组创建
int* a = (int*)malloc(sizeof(int)*5);
//释放内存
free(a);
//C++ 数组创建
int* a = new int[5];
//释放内存
delete[] a;
//C 类数组
int* a = (Study*)malloc(sizeof(Study)*5);
//释放内存
free(a);
//C++类数组
Study* a = new Study[5];
//释放内存
delete[] a;
C和C++写法的区别在于:
C的malloc函数只申请空间,不会执行析构函数和构造函数
C++的new,会执行构造和析构函数
引用类型就是变量的“别名”
基本类型
//如下:我有一个整数类型a
int a = 5;
//我给a一个别名
int& bac = a;
这里的int&就是一个引用类型
引用类型必须要初始化
我们不可以采用普通变量“双行赋值法”,只能定义的时候直接初始
类
class YinYong
{
public:
int a;
};
同样,修改引用类型的,就相当于修改了他本身
数组类型
#include
#include
int mian()
{
int a[] = {7,8,9};
int(&bac)[3] = a;
bac[3] = 100;
printf("%d \n", a[3]);
return 0;
}
指针类型
引用类型,就是变量的一个别名,修改引用类型,就是修改变量本身
引用就是变量的别名,实现的方法就是指针
int a = 1; //必须初始化
int* b = &a; //指针类型必须用&来接收变量a的地址
int& bac = a; //而引用类型不用,这里是差异之一
初始化赋值,没有任何区别(指针与引用)
指针是围绕着自己运算,他能代表自己,但是引用不可以
而且,指针+完宽度以后,存储的地址就变了
指针想怎么改怎么改,指针是独立的,引用看起来是独立的,但是他存储的永远都是代表的那个变量或者对象的真实地址,操作引用的时候,操作的永远都是变量或者对象原来的本身
小结
引用必须赋初始值
对引用修改,就是对指向的变量修改,而不是修改引用本身
对引用做运算,就是对指向的变量做运算,而不是本身做运算
引用就是一个“弱化的指针”
引用作用
函数参数传递
常引用
参考链接1:https://blog.csdn.net/qq_39642794/article/details/83269047
参考链接2:https://www.cnblogs.com/qianqiannian/p/6037520.html
子类继承父类,子类重写了父类的Test的函数
重写的目的在于:为了保证它们两个有共同的接口,并且子类可以实现父类没有实现的功能
如果父类可以满足需求的情况下,子类是没有必要重写的。
传递父类对象的时候,就会调用父类的函数
传递子类对象的时候,就会调用子类的函数
在C++里,想实现多态性,前提是必须让当前函数是个虚函数,而不是普通函数
#include
#include
class A
{
public:
int x;
void Test()
{
printf("A \n");
}
};
class B :public A
{
public:
void Test()
{
printf("B \n");
}
};
void Fun(A* p)
{
p->Test(); //多态
}
int main()
{
A a;
B b;
Fun(&b);
return 0;
}
下断、调试、查看反汇编
多态就是通过间接调用实现的
#include
#include
class A
{
public:
int x;
virtual void Test()
{
printf("A \n");
}
};
class B :public A
{
public:
void Test()
{
printf("B \n");
}
};
void Fun(A* p)
{
p->Test(); //多态
}
int main()
{
A a;
B b;
//Fun(&b);
printf("%d \n", sizeof(a));
return 0;
}
一共有一个成员x,函数不占空间,理应是4字节,但执行后,发现有8个字节
但是如果我们把虚函数标志virtual去掉,就是4字节了。
也就是当我们类里存在虚函数的时候,就会在原来的基础上多4个字节(和虚函数个数无关,一千个虚函数也是多4个字节,自己动手实验)
C++中之所以能实现多态,有两点最重要的
1、间接调用,没有间接调用,代码就是写死的,无法改变
2、有了间接调用,怎么改变,取决于虚表里面的值是什么
所以,多态的本质就是间接调用+虚表
一旦当前对象有虚函数,会在对象最开始的地方,有4字节,这4个字节指向一张表,称为虚表
虚表总结
1、只要类里包含虚函数,不论一个还是一千个,就会生成一张表,这张表的地址就存在当前对象里,存的是表的地址,不是表
2、虚表的位置存在当前对象最开始的位置,正常的话最开始的是数据成员
3、虚表结构就是,有几个虚函数,就往里面写几个虚函数的地址
4、虚表里存储的内容就是函数的地址
#include
#include
class Number
{
public:
Number(int a, int b)
{
this->a = a;
this->b = b;
}
bool Biger(Number& c) //改
{
return this->a > c.a&& this->b > c.b;
}
private:
int a;
int b;
};
int main()
{
Number a1(3, 3), a2(2, 2);
bool e = a1.Biger(a2); //改
return 0;
}
bool类型,bool类型占用1字节,只有两个值:true、false,true=1,false=0
operator> //这样就可以告诉编译器,我们要重载大于符号
运算符重载,本质也就是给运算符一个对应的函数
模板可以作用的两个区域:
函数内使用模板
结构体/类中使用模板
模板可以作用的两个区域:
函数内使用模板
结构体/类中使用模板
函数模板
template 返回类型 函数名(参数列表)
//一个模板固定前缀,后面是一个函数声明
{
函数体
}
模板的本质就是编译器帮我们生成x份不同的函数,根据传递的东西,找到不同的函数地址
虚函数
1、函数前面加上virtual,这个函数就会变成虚函数
2、没有函数体,后面跟0
class CBK {
public:
virtual double GetRv() = 0;
};
上面这个函数,没有函数体,直接跟=0结束,这样的函数称为纯虚函数
包含纯虚函数的类,都称为抽象类(Abstract Class)
抽象类里面也可以写普通函数
抽象类不能实体化
class CBK {
public:
virtual double GetRv() = 0;
};
上面这个纯虚函数,就没法使用如以下的语法:
CBK cbk; //全局或者栈创建对象
CBK* bcbk = new CBK(); //堆里创建对象
如果使用实体化语法,就会报错
抽象类看成是一个标准,任何该类的子类都必须尊重的标准
有时候当我们子类很多的时候,纯虚这个标准可以提供给我们方便
编程时存储对象可以用:数组、链表等
拷贝的本质其实就是内存复制
重载赋值运算符
对象拷贝的两种形式:
1、使用拷贝构造函数
2、使用“=”运算符
友元:https://www.runoob.com/cplusplus/cpp-friend-functions.html
友元friend 函数,把这个函数做一个声明,私有成员就可以被友元声明过的使用
在别的类里面定义的类,叫做内部类
#include
#include
class FObject
{
public:
FObject()
{
}
FObject(int a, int b)
{
this->a = a;
this->b = b;
}
class FObject2 //内部类
{
public:
FObject2()
{
}
FObject2(int c, int d, int e)
{
this->c = c;
}
private:
int c;
};
private:
int a;
int b;
};
int main()
{
printf("%d \n", sizeof(FObject));
return 0;
}
内部类FObject2和外部类FObject,有什么关系?
唯一的关系就是:内部类受到外部类的public或者private的影响
解决命名冲突的问题
原来的时候,我们知道两个函数命名不能一样
比如有一个函数名称叫做Test()
另一个函数的名字就不能叫Test()了
但是后来,我们学了类(Class)
这样就可以把不同函数命名于不同的类里,这样他们属于不同的类,就不会有命名冲突问题
但是大项目开发时候,避免不了的就是命名冲突问题
命名空间使用方法:
namespace 名称x
{
//全局变量
//类
//函数
}
namespace 名称y
{
//全局变量
//类
//函数
}
比如:
#include
#include
namespace Test1
{
int a = 100;
void Prf()
{
printf("Good! \n");
}
}
namespace Test2
{
int a = 100;
void Prf()
{
printf("Good 2 \n");
}
}
int main()
{
return 0;
}
不可能像原来那样直接输入a,因为是无法解析无法识别的,需要加上作用域或者作用范围符号::才能识别
int main()
{
printf("%d \n", Test2::a);
Test2::Prf();
return 0;
}
可以通过using namespace,可以省略作用范围的::的添加
void Fn()
{
using namespace Test1;
Prf();
}
int main()
{
using namespace Test2;
printf("%d \n", a);
Prf();
Fn();
return 0;
}
namespace Test2
{
int a = 100;
void Prf()
{
printf("Good 2 \n");
}
}
void Prf()
{
Test1::Prf(); //冲突的时候,虽然写了using namespace,但是编译器仍然无法识别,需要手动在写一次
}
int main()
{
using namespace Test2;
printf("%d \n", a);
::Prf(); //普通函数在全局空间,不属于任何命名空间等,所以直接加上两个::代表使用全局的普通函数
return 0;
}
简明释义:用static修饰的就是一个全局变量,只不过是一个私有的全局变量
全局变量的特点
分配在全局区,程序启动的时候这块内存就已经存在了
一个地方对全局变量做了修改,其他地方都有体现
static和全局变量唯一的区别就是:
1、全局变量在哪都可以访问,可以被所有东西使用
2、而经过static修饰的,只可以在当前函数 或者 当前文件下使用
static:https://www.runoob.com/w3cnote/cpp-static-usage.html