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++中struct
和class
唯一区别就在于默认的访问权限不同
区别:
- 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 指针
友元
运算符重载
继承
多态
文件操作