C++核心编程

针对 C++面向对象

内存分区模型

C++程序执行时,将内存大致划分为 4 个区域:

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

内存四区意义:
不同区域存放的数据,赋予不同的生命周期,给哦我们最大的灵活编程

程序运行前

程序编译后,生成exe可执行程序,未执行该程序前分为两个区域
代码区:

  • 存放 CPU 执行的机器命令

  • 代码区是共享的,共享的目的是对繁琐被执行的程序,只需要在内存中有一份代码即可

  • 代码区是只读的,使其只读的原因是防止程序意外的修改它的指令

    全局区:

  • 全局变量和静态变量存放在此

  • 全局区还包含了常量区,字符串常量和其他常量也存放在此

  • 该区域的数据在程序结束后由操作系统释放


#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 全局变量
int g_a = 20;
int g_b = 20;

// const 修饰的全局变量,全局常量
const int g_c = 30;

int main()
{
	//全局区:全局变量、静态变量、常量
	// 创建普通局部变量
	int a = 10;
	int b = 10;

	cout << "局部变量a的地址为:" << (int)&a << endl;
	cout << "局部变量a的地址为:" << (int)&b << endl;

	cout << "全局变量a的地址为:" << (int)&g_a << endl;
	cout << "全局变量a的地址为:" << (int)&g_b << endl;

	// 静态变量 在普通变量前面加 static 属于静态变量
	static int s_a = 10;
	static int s_b = 10;
	cout << "静态变量s_a的地址为:" << (int)&s_a << endl;
	cout << "静态变量s_b的地址为:" << (int)&s_b << endl;

	// 常量
	// 字符串常量
	cout << "字符串常量地址:" << (int)&"hello world" << endl;
	//cosnt 修饰的变量
	//const 修饰的全局变量,const 修饰的局部变量
	cout << "const 修饰的全局变量,全局常量" << (int)&g_c << endl;



	return 0;
    system("pause");
}

总结:

  • C++中在程序运行前分为全局区和代码区
  • 代码区特点是共享和只读
  • 全局区中存放全局变量、静态变量、常量
  • 常量区中存放 cosnt 修饰的全局变量和字符串常量

程序运行后

栈区: 由编译器自动分配释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放


#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 栈区数据注意事项----不要返回局部变量的地址
// 栈区数据由编译器管理开辟和释放
int* func(int b) { // 形参数据也会放在栈上
	b = 100;
	int a = 10; // 局部变量 存放在栈区,栈区的数据在函数执行完成后自动释放
	return &a; // 返回局部变量地址
}

int main()
{

	int *p = func(1);
	cout << *p << endl;
	cout << *p << endl;

    system("pause");
	return 0;
}

堆区:

  • 由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
  • 在 C++中主要利用 new 在堆区开辟内存

#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

int* functio() {
	//利用new关键字,可以将数据开辟到堆区
	int* a = new int(10);
	return a;
}

int main()
{
	int *p = functio();
	cout << *p << endl;
    system("pause");
	return 0;
}

new 操作符

  • C++中利用 new 操作符在堆区开辟数据
  • 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete
  • 语法:new 数据类型
  • 利用 new 创建的数据,会返回该数据对应的类型的指针

#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

int* functio() {
	// new 返回是该数据类型的指针
	int* a = new int(10);
	return a;
}

// 1.new 的基本语法
void test01() {
	int* p = functio();
	cout << *p << endl;
	//堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 `delete`
	delete p;

}
// 2.利用new关键字开辟数组
void test02() {
	// 创建10整形数组,在堆区
	int * arr = new int[10];
	for (int i = 0; i < 10; i++)
	{
		arr[i] = i + 100;
	}

	// 打印数据
	for (int i = 0; i < 10; i++)
	{
		cout << arr[i] << endl;
	}
	// 释放堆区数组,释放数组时需要添加[]
	delete[] arr;
}

int main()
{
	test01();

	test02();

    system("pause");
	return 0;
}

引用

引用的基本使用

作用:给变量起别名
语法:数据类型 &别名 = 原名


#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;


int main()
{
	int a = 10;
	int& b = a;

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

	b = 100;
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

    system("pause");
	return 0;
}

引用注意事项

  • 引用必须初始化
  • 引用初始化后,不可以改变

#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;


int main()
{
	//1.引用必须初始化

	int a = 10;
	int& b = a;
	//2.引用在初始化后,不可以修改
	int c = 20;
	b = c; // c的值并没有赋值给b

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

	b = 100;
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;

    system("pause");
	return 0;
}

引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参


#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 1.值传递
void mySwap01(int a, int b) {
	int temp = a;
	a = b;
	b = temp;

	cout << "mySwap01 a= " << a << endl;
	cout << "mySwap01 b= " << b << endl;
}

// 2.地址传递
void mySwap02(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

// 3.引用传递
void mySwap03(int &a, int &b) {
	int temp = a;
	a = b;
	b = temp;
}

int main()
{

	int a = 10;
	int b = 20;
	// 值传递, 形参不会修饰实参
	mySwap01(a,b);
	cout << "a= " << a << endl;
	cout << "b= " << b << endl;

	// 地址传递,形参可以修饰实参
	mySwap02(&a,&b);

	// 引用传递,形参也可以修饰实参
	mySwap03(a,b);


    system("pause");
	return 0;
}

引用做函数返回值

  • 作用:引用是可以作为函数的返回值存在的
  • 注意:不要返回局部变量引用
  • 用法:函数调用作为左值

#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 1.不用返回局部变量的应用
int& test01() {
	int a = 10; // 局部变量存放在四区中的栈区
	return a;
}

// 2.函数的调用可以作为左值
int& test02() {
	static int a = 10; // 静态变量,存放在全局区,全局区在程序结束后系统释放
	return a;
}

int main()
{

	int& ref = test01();
	cout << "ref=" << ref << endl; // 第一次结果正确,因为编译器做了保留
	cout << "ref=" << ref << endl; // 第二次结果错误,a的内存已经释放


	int& ref2 = test02();
	cout << "ref2=" << ref2 << endl;
	cout << "ref2=" << ref2 << endl;
	//函数的调用可以作为左值
	test02() = 1000; // 如果函数的返回值是引用,这个函数调用可以作为左值

	cout << "ref2=" << ref2 << endl;
	cout << "ref2=" << ref2 << endl;

    system("pause");
	return 0;
}

引用的本质

本质:引用的本质在 C++内部实现是一个指针常量,引用一旦初始化就不可以发生改变


#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 发现是引用,转换为 int* const ref = &a;
void func(int& ref) {
	ref = 100; // ref是引用,转换为*ref = 100
}

int main()
{
	int a = 10;
	// 自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改
	int& ref = a;
	ref = 20;// 内部发现 ref 是引用,自动帮我们转换为: *ref = 20;

	cout << "a:" << a << endl;
	cout << "ref:" << ref << endl;

	func(a);

  system("pause");
	return 0;
}

常量引用

作用:常量引用主要用来修饰形参,防止误操作。在函数形参列表中,可以加 const 修饰形参,防止形参改变实参


#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 打印函数数据 const 修饰形参,不允许修改
void showValue(const int& val) {
	//val = 100;
	cout << "val=" << val << endl;
}

int main()
{
	// 引用使用的场景,通常用来修饰形参,防止误操作
	int a = 10;
	// 加上 cosnt 之后,编译器将代码修改 int temp = 10; const int &ref = temp;
	const int& ref = 10; // 引用必须引一块合法的内存空间

	int b = 10;
	showValue(b);
	cout << "b=" << b << endl;

    system("pause");
	return 0;
}

函数提高

函数默认参数

在 C++中,函数的形参列表中的参数可以设置默认值。语法返回值类型 函数名(参数 = 默认值) {}
注意事项

  • 1.如果某个位置已经存在了默认参数,那么从这个位置往后,从左到右都必须有默认值
  • 2.如果函数声明有默认参数,函数实现就不能够有默认参数

#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 如果某个位置已经存在了默认参数,那么从这个位置往后,从左到右都必须有默认值
int pushFunction(int a, int b = 20, int c = 10) {
	return a + b + c;
}

// 如果函数声明有默认参数,函数实现就不能够有默认参数
// 声明和实现只能有一个设置默认参数,否则编译器报错
int functTwo(int a = 10, int b = 10);
int functTwo(int a, int b) {
	return a + b;
}

int main()
{
	int a = 20;
	int result = pushFunction(a);
	cout << result << endl;


	int reslut_a = functTwo();
	cout << reslut_a << endl;

    system("pause");
	return 0;
}

函数展位参数

  • C++中函数的形参列表中可以有占位参数,用来占位,调用函数时必须填补该位置。
  • 语法:返回值类型 函数名(数据类型){}

#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 占位参数  --- 返回值类型 函数名(数据类型){}
// 目前阶段的占位参数,用不到,占位参数还可以设置默认值
void func(int a, int = 10) {
	cout << "this is function" << endl;
}

int main()
{
	func(20, 10);

    system("pause");
	return 0;
}

函数重载

函数重载描述

作用:函数名可以相同,提高复用
函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数类型不同,或者个数不同或者顺序不同

注意:函数的返回值不可以作为函数重载的条件


#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 函数重载,提高复用。函数重载需要函数都在一个作用域下

//函数重载满足条件:
//同一个作用域下
//函数名称相同
//函数 类型不同,或者 个数不同  或者  顺序不同
//存在返回值的函数,不允许进行函数重载
void func() {
	cout << "func 的调用" << endl;
}

void func(int a) {
	cout << "func(int a)的调用" << endl;
}

// 传递参数顺序不同
void func(int a, double b) {
	cout << "func(int a, double b)的调用" << endl;
}

void func(double b, int a) {
	cout << "func(int a, double b)的调用" << endl;
}




int main()
{
	func();
	func(10);

    system("pause");
	return 0;
}

函数重载注意事项

  • 引用作为函数重载条件
  • 函数重载碰到函数默认参数

#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

///////////////引用作为函数重载条件/////////////////
void func(int &a) {
	cout << "func(int &a)的调用=" << &a << endl;
}
void func(const int& a) {
	cout << "func(const int& a)的调用" << endl;
}

///////////////函数重载碰到函数默认参数/////////////////
void func2(int a, int b = 10) {
	cout << "func2(int a)的调用" << endl;
}

void func2(int a) {
	cout << "func2(int a)的调用" << endl;
}

int main()
{


	// 调用 void func(int &a)
	/*int a = 10;
	func(a);*/

	// 调用 void func(const int& a)
	//func(10);

	//func2(10); // 当函数重载遇到默认参数,出现二义行,报错,尽量避免

    system("pause");
	return 0;
}

类和对象

C++面向对象的三大特性为:封装、多态、继承
C++认为万事万物皆为对象,对象存在其属性和行为
例如:人可以作为对象,属性有姓名、年龄、身高、体重,...行为有走、跑、跳、吃饭等 具有相同性质的对象,我们可以抽象为类

封装

封装的意义

封装是 C++面向对象三大特性之一
封装的意义:

  • 将属性和行为作为一个整体,表现生活中的事务
  • 将属性和行为加以权限控制

封装第一层含义: 在设计类的时候,属性和行为写在一起,表现事务 语法:class 类名{访问权限:属性/行为};
**示例 1:**设置一个圆类,求圆的周长


#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 设置一个圆类,求圆周长
// 圆周长公式:2 * PI * 半径

//圆周率
const double PI = 3.1415926;

//class 代表设计一个类,类后面跟着类名称
class Circle {
	// 访问权限
	// 公共权限
public:
	// 属性:半径
	int m_r;

	// 行为:获取圆的周长
	double calculateZC() {
		return 2 * PI * m_r;
	}
};

int main()
{
	// 同通过圆类 创建具体的圆(对象)
	Circle c1;
	// 给圆对象的属性进行赋值
	c1.m_r = 10;

	cout << "圆周长=====" << c1.calculateZC() << endl;

    system("pause");
	return 0;
}

**示例 2:**设置一个学生类,属性有姓名、学号、可以给姓名和学号赋值。可以显示学生姓名和学号


#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

// 设置一个学生类,属性有姓名、学号、可以给姓名和学号赋值。可以显示学生姓名和学号

// 类中属性和行为我们统一称为成员
//属性 成员属性 成员变量
//行为 成员函数 成员方法

//class 代表设计一个类,类后面跟着类名称
class Student {
	// 访问权限:公共权限
	public:
	// 属性:姓名、学号
	string m_Name;
	int m_Id;

	// 行为:获取学生学号和姓名
	void showStudent() {
		cout << "名字:" << m_Name << " 学号:" << m_Id << endl;;
	}

	// 给姓名赋值
	void setName(string name) {
		m_Name = name;
	}
	// 给学号赋值
	void setId(int id) {
		m_Id = id;
	}
};

int main()
{
	// 同通过学生类 创建具体的学生(对象)
	Student s1;
	// 给学生对象的属性进行赋值
	s1.setName("李四");
	int id = 2016084512;
	s1.setId(id);

	s1.showStudent();

    system("pause");
	return 0;
}

封装第二层含义:
类在设计时,可以八属性和行为放在不同权限下,加以控制
访问权限有三种:

  • pubilc 公共权限 类内可访问 类外可访问
  • protected 保护权限 类内可访问 类外不可以访问
  • private 私有权限 类内可访问 类外不可访问
#include <iostream>
using namespace std;

/*
	pubilc 公共权限 类内可访问 类外可访问
	protected 保护权限 类内可访问 类外不可以访问 子类可以访问父类中的保护内容
	private 私有权限 类内可访问 类外不可访问 子类不可以访问父类中的私有内容
*/

class Person {
public:
	// 公共权限
	string m_Name; // 姓名

protected:
	// 保护权限
	string m_Car; // 汽车
private:
	// 私有权限
	int m_Password; // 银行卡密码
public:
	void fuinc() {
		m_Name = "张三";
		m_Car = "奔驰";
		m_Password = 457787;
	}
};

int main()
{
	// 实例化具体对象
	Person p1;
	p1.m_Name = "姓名";
	//p1.m_Car = "44545" // 保护权限内容,在类外不可访问
	//p1.m_Password = 451274 // 私有权限,在类外不可访问

	cout << "=======" << endl;
}

struct 和 class 区别

C++中structclass唯一区别就在于默认的访问权限不同
区别:

  • struct 默认权限为公共
  • class 默认权限为私有
#include <iostream>
using namespace std;

struct C1 {
	int m_A; // 默认是公有权限
};

class C2 {
	int m_A; // 默认是私有权限
};

int main()
{
	C1 c1;
	c1.m_A = 100;

	C2 c2;
	//c2.m_A = 100; // c2 为 class 创建类默认为私有类外不可访问
}

成员属性设置为私有

优点:

  • 将所有成员属性设置为私有,可以自己控制读写权限
  • 对于写权限,我们可以检测数据的有效性
#include <iostream>
using namespace std;
#include <string>
// 成员属性设置为私有
//1.可以自己控制读写权限
//2.对于写可以检测数据的有效性

class Person {
public:
	// 写姓名
	void setName(string name) {
		m_Name = name;
	}
	// 读姓名
	string getName() {
		return m_Name;
	}


	// 设置年龄 可读可写,如果修改年龄范围必须在0~150之间
	void setAge(int age) {
		if (age < 0 || age > 150) {
			return;
		}
		m_Age = age;
	}

	// 获取年龄
	int getAge() {
		//m_Age = 0; // 初始化为0岁
		return m_Age;
	}

	// 设置喜好
	void setLover(string lover) {
		m_Lover = lover;
	}

private:
	string m_Name; // 可读可写
	int m_Age; // 只读
	string m_Lover; // 只写
};

int main()
{

	Person p;
	p.setName("张三");
	cout << "姓名为:" << p.getName() << endl;

	p.setAge(1100);
	cout << "年龄为:" << p.getAge() << endl;

	// 只写,只能在class内进行访问到
	p.setLover("喜欢汽车");


	//p.m_Name = "张三";

	return 0;
}

练习案例 1:设计立方体类(Cube)求出立方体面积和体积,分别用全局函数和成员函数判断两个立方体是否相等

#include <iostream>
using namespace std;
#include <string>

// 设计立方体类(Cube)求出立方体面积和体积,分别用全局函数和成员函数判断两个立方体是否相等
class Cube {
	// 行为 获取里立方体面积和体积
public:
	// 设置长
	void setL(int l) {
		m_L = l;
	}
	// 获取长
	int getL() {
		return m_L;
	}

	// 设置宽
	void setW(int w) {
		m_W = w;
	}
	// 获取宽
	int getW() {
		return m_W;
	}

	// 设置高
	void setH(int h) {
		m_H = h;
	}

	// 获取高
	int getH() {
		return m_H;
	}

	// 获取立方体面积
	int calculateS() {
		return 2 * m_L * m_W + 2 * m_W * m_H + 2 * m_L * m_H;
	}

	// 获取立方体体积
	int calculateV() {
		return m_L* m_W* m_H;
	}

	// 利用成员函数判断两个立方体是否相等
	bool isSameByClass(Cube &c) {
		if (getL() == c.getL() && getW() == c.getW() && getH() == c.getH()) {
			return true;
		}
		return false;
	}

	// 属性
private:
	int m_L;
	int m_W;
	int m_H;


};


// 利用全局函数判断两个立方体是否相等 ,采用值传递方式传递参数
bool isSame(Cube &c1, Cube &c2) {
	if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()) {
		return true;
	}
	return false;
}

int main()
{

	// 创建立方体对象
	Cube c1;
	c1.setL(10);
	c1.setW(10);
	c1.setH(10);

	cout << "C1的面积为:" << c1.calculateS() << endl;
	cout << "C1的体积为:" << c1.calculateV() << endl;


	Cube c2;
	c2.setL(10);
	c2.setW(10);
	c2.setH(11);

	cout << "C2的面积为:" << c2.calculateS() << endl;
	cout << "C2的体积为:" << c2.calculateV() << endl;
	// 利用全局函数判断立方体是否相等
	bool result = isSame(c1, c2);
	if (result) {
		cout << "全局函数==c1和c2是相等的" << endl;
	}
	else {
		cout << "全局函数==c1和c2不相等" << endl;
	}

	// 利用成员函数判断立方体是否相等
	bool ret = c1.isSameByClass(c2);
	if (ret) {
		cout << "类成员函数==c1和c2是相等的" << endl;
	}
	else {
		cout << "类成员函数==c1和c2不相等" << endl;
	}


	system("pause");
	return 0;
}

对象的初始化和清理


C++对象模型和 this 指针


友元


运算符重载


继承


多态


文件操作