C++(五)——核心总结:内存分区&引用&函数提高&类和对象
目录
C++核心编程
- 空的模板程序
1、内存分区模型
- 内存分为四个分区:代码区,全局区,栈区,堆区
- 简单关系
- 运行程序前
- 代码区
- 存放函数体的二进制代码,由操作系统管理
- 代码区是共享的:内存中一个代码就好了
- 代码区是只读的:防止程序修改指令
- 全局区
- 存放全局变量和静态变量和常量
- 常量区,字符串常量还有其他的常量该区域的数据在程序结束后操作系统释放
- 程序运行后
- 栈区
- 函数的参数和局部变量存在栈区
- 代码区
- 运行程序前
- 全局区
- 存放全局变量,静态变量,常量(const修饰的全局常量和字符串常量)
- 局部常量存放在栈区,局部变量也存在栈区
- 栈区
- 局部变量和常量
- 不要反悔栈区的地址,因为函数执行完毕之后,栈区的数据会自动释放,编辑器不会进行二次保留
- 堆区
- 程序员进行决定数据是否释放
- 用new开辟堆区内存
- 堆区的数据使用后记得进行释放,不然可能会造成内存泄露
- 一般的数据开辟
int *p=new int(10)
- 使用结束后进行内存的释放
delete p;
- 一般的数据开辟
2、引用
- 基本语法:
//创建变量
int a = 10;
//创建引用
int &b = a;
- 引用的本质——指针常量
int a = 10;
int *const b = a;
- 引用的特点:指向一个变量之后不可以再指向其他的变量
- 常量的引用:结合指针,常量指针常量,指向和值都不可以修改
const int &ref = 10;
const int *const ref = 10;
3、函数提高
- 函数的默认参数
- 在定义有多个参数的函数的时候,同时进行赋值,如果接下来使用函数的时候不赋值,则默认数值参与运算
- 函数的占位参数
- 只写一个数据类型进行占位,不指定具体参数
void func13_1(int a,int=20)
- 只写一个数据类型进行占位,不指定具体参数
- 函数的重载
- 同一个作用域,参数名称相同,返回值相同,参数类型不同,个数不同的函数可以安排重写
- 引用可以作为重载的条件
- 默认函数的重载在使用的过程中会产生二义性,需要注意
//引用的重载
func(int &a);
func(const int &a);
//主函数:
int a=10;
func15_1(a);
func15_1(10);//直接传输常量,完成重载
======================
//默认函数的重载
void func15_2(int a,int b=10) {
cout << "func15_2(int a,int b)调用" << endl;
}
void func15_2(int a) {
cout << "func15_2(int a)调用" << endl;
}
//主函数
func15_2(10);//出现了二异性,所以不行
4、类和对象
1、封装(类的定义)
- 定义:将属性或者行为作为一个整体
- 类:类中的属性和行为称之为成员
- 属性:成员属性 成员变量
- 行为:成员函数 成员方法
- 类:类中的属性和行为称之为成员
- 访问权限
- public 公共权限 成员类内部可以访问 类外也可以访问
- protect 保护权限 类内部可以访问 子类可以访问父类保护的内容
- private 私有权限 只有类内可以访问 子类不可以访问
- struc和class的区别
- struct默认访问权限是共有的
- class的默认访问权限是私有的
- 成员属性
- 权限设置有利于封装:实现对象的属性进行赋值;而对象之外不可以赋值
- 通过设置公共函数为接口维护私有变量
class Person20 {
public:
//写姓名
void setName(string m_name) {
name = m_name;
}
//读姓名
string getName(string m_name) {
return m_name;
}
private:
//姓名可读可写的
string name;
int age;
string Lover;
};
//主函数
Person20 p;
p.setName("张三");
cout << p.getName("张三") << endl;
2、对象的初始化和清理
1.析构函数和构造函数
- 定义
- 两个函数类中有默认的空的构造和析构
- 构造函数是在对象生成时自动执行内部的语句
- 析构函数是对象进行销毁的过程的最后一部调用的代码
- 分类
- 构造函数
- 有参构造和无参构造
- 普通构造函数和拷贝构造函数
- 构造函数
Person() {
cout << "构造函数的调用" << endl;
}
//对象在销毁前调用析构函数的代码
~Person() {
cout << "析构函数的调用" << endl;
}
2.构造函数
- 有参无参构造 传入参数与否
//分类:无参构造 有参构造
person() {
cout << "无参构造函数" << endl;
}
person(string name) {
cout << "有参构造函数" << endl;
m_name = name;
}
person(int age) {
cout << "有参函数构造二" << endl;
}
- 普通构造函数&拷贝构造函数
- 普通构造:直接进行构造
- 拷贝构造:传入对象,将传入对象的值拷贝到当前对象
//分类2:普通构造函数&拷贝构造函数
//普通构造函数
person() {
cout << "普通构造函数" << endl;
}
//拷贝构造函数,引用的方法拷贝给另一个对象
person(const person &p) {
cout << "拷贝构造函数"<<endl;
m_name = p.m_name;//传入的人的属性拷贝到当前对象上
}
- 拷贝构造函数
- 拷贝构造函数通常用于利用已经构造好的对象快速生成一个新的对象
- 针对于新的对象,会拷贝原来对象的所有属性
person25(const person25 & p) {
cout << "person的默认拷贝构造函数" << endl;
m_age = p.m_age;
}
3.调用构造函数
- 括号调用
- 显示法
- 隐式转换法
//括号调用
person p1;//默认函数构造
person p2("张三");//括号法构造
person p3(p2);//拷贝构造函数
//注意1:调用默认构造函数不用小括号
//下面的函数编译器认为是函数的声明,不会创建对象。
//person p1();
//显示调用
person p4;
person p5 = person("李四");//有参构造
person p6 = person(p5);//拷贝构造函数
person("王五");//匿名对象,当前行执行结束后系统会立刻回收对象
//注意2:不用利用拷贝函数初始化匿名对象
//编译器认为这是对象的声明
//person(p3);
//隐式转换调用
person p7 = 18;//适用于数值,不适用于字符串,有参构造
person p8 = p7;//拷贝构造
- 几种调用关系的存在情况
//c++会默认添加三种构造函数:默认的解析和析构函数&默认的拷贝构造函数
//用户定义有参构造函数,C++不提供无参构造函数,会提供拷贝构造函数
//用户定义拷贝构造函数,C++不会提供其他的构造函数
4.深浅拷贝
- 定义
- 浅拷贝:简单的赋值拷贝工作
- 深拷贝:在堆区重新开发空间,进行拷贝工作
- 深浅拷贝处理
- 深浅拷贝在堆区开辟内存,当使用该对象结束时需要通过析构函数自行释放堆区的内存
//成员属性
int m_age;//整型变量
int *m_height;//指针类型的变量
//基本的构造函数
person27(int age, int height) {
m_age = age;
m_height=new int(height);//堆区存放的是地址
}
//浅拷贝数据
//默认的拷贝构造函数
person27(const person27&p){
m_age=p.m_age;//整形的拷贝
m_height = p.m_height;//指针的拷贝
}
//浅拷贝的内存释放
~person27() {
if (m_height != NULL){
delete m_height;
m_height = NULL;//置空是为了防止野指针的错误
}
cout << "析构函数" << endl;
}
//主函数实现
person27 p2(p1);//默认是浅拷贝
//报错是因为堆区的指针所指向的数据重复释放
//深拷贝数据
person27(const person27&p) {
m_age = p.m_age;
m_height = new int(*p.m_height);//在堆区开辟另一块内存存放拷贝过来的数据,即可避免内存的重复释放问题,两个数据的地址不再相同
//对指针的内容进行深拷贝工作
cout << "拷贝构造函数" << endl;
}
- 示意图
5.类的几个函数的构造规则
- 一个类进行定义后会产生三种函数:
- 构造函数
- 析构函数
- 拷贝构造函数
- 定义有参构造函数之后(不会提供无参构造,会提供析构和拷贝)
- 定义拷贝构造函数(不会提供析构和构造)
6.初始化成员列表
- 定义:对于实例化对象后的所有的成员变量进行赋值操作
- 具体方法:
- 类中对构造函数进行重写
- c++自带的初始化语法
//类的定义
class person28 {
public:
int m_A;
int m_B;
int m_C;
}
//1.对类中的构造函数重写
class person28 {
public:
int m_A;
int m_B;
int m_C;
person28(int a,int b,int c)//传统赋初值的方法
{
m_A = a;
m_B = b;
m_C = c;
}
}
//主函数中调用
person28 p1(1,2,3);
//2.使用初始化列表
//不重写构造函数
class person28 {
public:
int m_A;
int m_B;
int m_C;
person28():m_A(10), m_B(20), m_C(30){}//初始化列表的方法
person28(int a,int b,int c):m_A(10), m_B(20), m_C(30){}//更灵活的方法
}
//主函数中调用
person28 p1;
person28 p2(10,20,20);
7.类对象作为类成员
- 类可以在另一个类中进行实例化对象,作为该类的一个成员存在
8.静态成员
- 特点:所有的对象共用一个数据
- 编译之前进行分配内存,分配在了全局区
- 类内声明,类外初始化
- 不同的对象之间静态数据共享
- 两种不同的访问方式:对象访问和类名访问
//静态成员变量
class Person30 {
public:
static int m_A;
//private:
//static int m_A;
//静态成员变量存在访问权限,私有作用域类外无法访问
//即静态成员A的实际表示为注释方式
};
//类外进行了初始化
int Person30::m_A=100;
//数据共享
Person30 p1;
cout << p1.m_A << endl;//100
Person30 p2;
cout << p2.m_A << endl;//100
//静态变量有两种访问方式
Person30 p1;//1 对象进行访问
cout << p1.m_A << endl;
cout << Person30::m_A << endl;//2 类名进行访问
9.静态成员函数
- 类似于静态成员变量
//1 所有对象共用同一个函数
//2 静态的成员函数只能访问静态的成员变量
class Person31 {
public:
//静态的成员函数可以访问静态的成员变量
static void func() {
m_A = 100;
//静态的成员函数不可以访问非静态的成员变量
//m_B = 200;//产生了二异性,无法区分是哪一个对象的m_B属性。
cout << "static void func()的调用" << endl;
}
static int m_A;
int m_B;
//静态成员函数存在访问权限
private:
static void func2() {}//需要类外初始化的原因,是因为静态是私有的
};
int Person31::m_A=0;
void test31_01() {
Person31 p1;
//1 对象访问
p1.func();
//2 类名访问
Person31::func();
//类外访问不了私有的静态
//Person31::func2();
}
3、C++对象模型和this指针
1.成员变量和成员函数
- 空对象占用内存空间是一个字节
- 静态成员变量&函数不占用对象的内存空间
- 代码实现
#include<iostream>
#include<string>
using namespace std;
//类中的变量和函数是分开存储的
//非静态成员变量
class Person01 {
public:
int m_A;
static int m_B;
void func() {
}
static void func2() {
}
};
int m_B;
void test01_01() {
Person01 p;
//空对象占用的内存空间为:1 一个字节
//C++编译器会给每一个空对象分配一个字节空间,为了区分空对象占据内存的位置
//每个空对象有着独一无二的内存地址
cout << "size of p " << sizeof(p) << endl;
}
void test01_02(){
Person01 p;
//对象占用的内存空间为:4 四个字节
//静态成员变量不占用对象内存
//成员函数不占用对象的内存
//静态成员函数不占用对象的内存
cout << "size of p " << sizeof(p) << endl;
}
int main01() {
// test01_01();
test01_02();
system("pause");
return 0;
//综上:非静态成员变量属于类的对象上
2.this指针
- 指向被调用的成员函数所属的对象
- 解决名称冲突
- 返回对象本身使用
*this
- 代码实现
#include<iostream>
#include<string>
using namespace std;
//this指向被调用的成员函数所属的对象
//this指针隐含在每一个非静态的成员函数内的一种指针
//this直接使用,不需要定义
//1 解决名称冲突
//2 返回对象本身使用*this
class Person02 {
public:
Person02(int age) {
//this指向 被调用的成员函数 所属的对象
this->age = age;//1 解决名称冲突
}
//用对象会发生浅拷贝,导致结果无法累加
//返回本体用引用的方式返回
Person02& PersonAddAge(Person02 &p) {
this->age += p.age;
//this指向的是p2的指针,*this指向是p2对象本体即对this的解引用
return *this;
// return this;重复返回该对象
}
int age;//this的对应对象
};
void test02_01() {
Person02 p1(18);
cout << p1.age << endl;
}
void test02_02() {
Person02 p1(10);
Person02 p2(15);
//链式编程思想
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
cout << p2.age << endl;
}
int main02() {
test02_02();
system("pause");
return 0;
}
3.空指针调用成员函数
- 空指针下会出现限制内存访问的错误,所以如果调用this注意预防空指针错误
- 代码实现
#include<iostream>
#include<string>
using namespace std;
//空指针调用成员函数
class Person03 {
public:
void showClassName() {
cout << "this is Person class" << endl;
}
void showPersonAge() {
//空指针下无法访问内部的属性this
//防止空指针出错
if(this == NULL){
return;
}
cout << "age= " << this->m_Age<<endl;
}
int m_Age;
};
void test03_01() {
Person03*p=NULL;
p->showClassName();
p->showPersonAge();
}
int main() {
test03_01();
system("pause");
return 0;
}
4.const修饰成员函数
- const修饰之后是常函数和常对象
- this指针的本质 指针常量 指针的指向是不可以修改的
- const下,函数体内部无法修改对象
- 特殊变量想在常函数内部修改需要加上一个关键字mutable
mutable int m_B;
- 代码实现
#include<iostream>
#include<string>
using namespace std;
//const修饰之后是常函数和常对象
//常函数
//常对象
class Person04 {
public:
//this指针的本质 指针常量 指针的指向是不可以修改的
//const Person* const this;
//成员函数 后面加上const,修饰的是this指针,让指针指向的值不可以修改
void showPerson() const
{
//const下,函数体内部无法修改对象
//this->m_A = 100;
// this = NULL;//this指针不可以修改指针的指向
this->m_B = 100;
}
int m_A;
mutable int m_B;//特殊变量想在常函数内部修改需要加上一个关键字mutable
void func()const {
}
void func2(){
}
};
void test04_01() {
Person04 p;
p.showPerson();
}
void test04_02() {
const Person04 p;//对象前加const,变成了常对象
//p.m_A = 100;
p.m_B = 100;
//常对象只能调用常函数
p.showPerson();
p.func();
//p.func2();//常不可以调用普通成员函数,因为普通的成员函数可以修改属性
}
int main04() {
test04_01();
test04_02();
system("pause");
return 0;
}
4、友元
1.全局函数作友元
- 全局函数可以访问类里私有的成员
- 代码实现
#include<iostream>
#include<string>
using namespace std;
//友元的实现
//全局函数作友元
//房屋的类
class Building {
//全局函数可以访问类里私有的成员
friend void GoodGay(Building *building);
public:
string m_SittingRoom;
public:
Building() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
private:
string m_BedRoom;
};
//全局函数
void GoodGay(Building *building) {
cout << "好基友的全局函数正在访问:" << building->m_SittingRoom << endl;
//私有变得可以访问
cout << "好基友的全局函数正在访问:" << building->m_BedRoom<< endl;
}
void test01() {
Building building;
GoodGay(&building);
}
int main01() {
test01();
system("pause");
return 0;
}
友元类
- 代码实现
#include<iostream>
#include<string>
using namespace std;
//类作为友元
class Building_2 {
//GoodGay是本类的好朋友,可以访问本类的私有成员
friend class GoodGay;
public:
Building_2();
string m_SittingRoom;
private:
string m_BedRoom;
};
class GoodGay {
public:
GoodGay();
Building_2* building;
public:
//参观函数访问Building中的属性
void visit();
};
//类外写成员函数
Building_2::Building_2() {
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay::GoodGay() {
//创建一个建筑物的对象
building = new Building_2;
}
void GoodGay::visit() {
cout << "好基友正在访问:" << building->m_SittingRoom << endl;
cout << "好基友正在访问:" << building->m_BedRoom << endl;
}
void test02_1() {
GoodGay gg;
gg.visit();
}
int main02() {
test02_1();
system("pause");
return 0;
}
3.成员函数作友元
#include<iostream>
#include<string>
using namespace std;
//需要先声明类,指针指向的类
class Building03;
class GoodGay03 {
public:
GoodGay03();
void visit03_01();//访问Building03私有的成员
void visit03_02();//不访问Building03私有的成员
Building03*building;
};
class Building03 {
friend void GoodGay03::visit03_01();
public:
Building03();
public:
string m_SettingRoom;
private:
string m_BedRoom;
};
//类外实现成员函数
Building03::Building03() {
m_SettingRoom = "客厅";
m_BedRoom = "卧室";
}
GoodGay03::GoodGay03() {
building = new Building03;
}
void GoodGay03::visit03_01() {
cout << "访问公共的内容: " << building->m_SettingRoom << endl;
cout << "访问私有的内容: " << building->m_BedRoom;
}
void test03_01() {
GoodGay03 gg;
gg.visit03_01();
}
int main() {
test03_01();
return 0;
}