1 内存分区模型
C++程序在执行时,将内存大方向分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:
- 不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
1.1 程序运行前
在程序编码后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
- 存放CPU执行的机器指令
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
- 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
- 全局变量和静态变量存放在此
- 全局区还包含了常量区,字符串常量和其他常量也存放在此
- 该区域的数据在程序结束后由操作系统释放
#include<iostream> using namespace std; //全局变量 int g_a = 10; int g_b = 10; //const修饰的全局变量,全局常量 const int c_g_a = 10; const int c_g_b = 10; int main() { //全局区 //全局变量、静态变量、常量 //创建普通局部变量 int a = 10; int b = 10; cout << "局部变量a的地址为:" << (int)&a << endl; cout << "局部变量b的地址为:" << (int)&b << endl; cout << "全局变量g_a的地址为:" << (int)&g_a << endl; cout << "全局变量g_b的地址为:" << (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; //const修饰的变量 //const修饰的全局变量 const修饰的局部变量 cout << "全局常量c_g_a的地址为:" << (int)&c_g_a << endl; cout << "全局常量c_g_b的地址为:" << (int)&c_g_b << endl; //const修饰的局部变量 const int c_l_a = 10; // const int c_l_b = 10; cout << "局部常量c_l_a的地址为:" << (int)&c_l_a << endl; cout << "局部常量c_l_b的地址为:" << (int)&c_l_b << endl; system("pause"); return 0; }
局部变量a的地址为:7601824 局部变量b的地址为:7601812 全局变量g_a的地址为:12173364 全局变量g_b的地址为:12173368 静态变量s_a的地址为:12173372 静态变量s_b的地址为:12173376 字符串常量的地址为:12163932 全局常量c_g_a的地址为:12164744 全局常量c_g_b的地址为:12164748 局部常量c_l_a的地址为:7601800 局部常量c_l_b的地址为:7601788 请按任意键继续. . .
总结:
- C++中在程序运行前分为全局区和代码区
- 代码区特点是共享和只读
- 全局区中存放全局变量、静态变量、常量
- 常量区中存放const修饰的全局常量和字符串常量
1.2 程序运行后
栈区:
- 有编译器自动分配释放,存放函数的参数值,局部变量等
- 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
#include<iostream> using namespace std; //栈区数据注意事项 ---不要返回局部变量的地址 //栈区的数据由编译器管理开辟和释放 //int* func(int b) //形参数据也会放在栈区 int* func() { //b = 100; int a = 10; //局部变量 存放在栈区,栈区的数据在函数执行完后自动释放 return &a; //返回局部变量的地址 } int main() { //接受func函数的返回值 int* p = func(); cout << *p << endl; //第一次可以打印正确的数字,是因为编译器做了保留 cout << *p << endl; //第二次输出错误 system("pause"); return 0; }
10 2038286832 请按任意键继续. . .
堆区:
- 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
- 在C++中主要利用new在堆区开辟内存
#include<iostream> using namespace std; int * func() { //利用new关键字 可以将数据开辟到堆区 //指针 本质也是局部变量,放在栈上,指针保存的数据是放在堆区 int * p = new int(10); return p; //返回地址 } int main() { //在堆区开辟数据 int *p = func(); //接受地址 cout << *p << endl; cout << *p << endl; system("pause"); return 0; }
10 10 请按任意键继续. . .
总结:
- 堆区数据由程序员管理开辟和释放
- 堆区数据利用new关键字进行开辟内存
1.3 new操作符
- C++中利用new操作符在堆区开辟数据
- 堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
- 语法:
new 数据类型
- 利用new创建的数据,会返回该数据对应的类型的指针
#include<iostream> using namespace std; //1、new的基本语法 int* func() { //在堆区创建整型数据 //new返回时 该数据类型的指针 int* p = new int(10); return p; //返回地址 } void test01() { int* p = func(); cout << *p << endl; cout << *p << endl; //堆区的数据 由程序员管理开辟,程序员管理释放 //如果想释放堆区的数据,利用关键字delete delete p; //cout << *p << endl; //报错 } //2、在堆区利用new开辟数组 void test02() { //创建10整型数据的数组,在堆区 int* arr = new int[10]; //10代表数组有10个元素 for (int i = 0; i < 10; i++) { arr[i] = i + 100; //给10个元素赋值 100-109 } for (int i = 0; i < 10; i++) { cout << arr[i] << endl; } } int main() { test01(); test02(); system("pause"); return 0; }
10 10 100 101 102 103 104 105 106 107 108 109 请按任意键继续. . .
2 引用
2.1 引用的基本使用
作用: 给变量起别名
语法:数据类型 &别名 = 原名
#include<iostream> using namespace std; int main() { //引用基本语法 //数据类型 &别名 = 原名 int a = 10; int &b = a; //a起别名为b cout << "a=" << a << endl; cout << "b=" << b << endl; b = 100; cout << "修改b之后:" << endl; //修改别名存储的数据,原数据跟着改变 cout << "a=" << a << endl; cout << "b=" << b << endl; system("pause"); return 0; }
a=10 b=10 修改b之后: a=100 b=100 请按任意键继续. . .
2.2 引用注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变(b称为a的别名之后,不能再是c的别名)
#include<iostream> using namespace std; int main() { int a = 10; //1、引用必须初始化 int& b = a; //初始化为a //2、引用在初始化后,不可以改变 int c = 20; b = c; //赋值操作,而不是更改引用 //int& b = c; cout << "a=" << a << endl; cout << "b=" << b << endl; cout << "c=" << c << endl; system("pause"); return 0; }
a=20 b=20 c=20 请按任意键继续. . .
2.3 引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
#include<iostream> using namespace std; //交换函数 //1、值传递 void mySwop01(int a, int b) { int temp = a; a = b; b = temp; cout << "swap01 a=" << a << endl; cout << "swap02 b=" << b << endl; } //2、地址传递 void mySwop02(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } //3、引用传递 void mySwop03(int& a, int& b) { int temp = a; a = b; b = temp; } int main() { int a = 10; int b = 20; //mySwop01(a, b); //实参未改变,形参改变 //mySwop02(&a, &b); //实参为地址 mySwop03(a, b); cout << "a=" << a << endl; cout << "b=" << b << endl; system("pause"); return 0; }
a=20 b=10 请按任意键继续. . .
总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单。
2.4 引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
#include<iostream> using namespace std; //引用做函数的返回值 //1、不要返回局部变量的引用 int& test01() //相当于现在test01()函数是 a的别名了,a可以做左值,函数当然也可以 { 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; //ref2是a的别名 test02() = 1000; //如果函数的返回值是引用,这个函数调用可以作为左值 cout << "ref2=" << ref2 << endl; cout << "ref2=" << ref2 << endl; system("pause"); return 0; }
ref=10 ref=2059717104 ref2=10 ref2=10 ref2=1000 ref2=1000 请按任意键继续. . .
2.5 引用的本质
本质:引用的本质在C++内部实现是一个指针常量
2.6 常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
#include<iostream> using namespace std; //打印数据的函数 void showValue(const int& val) { //val = 1000; //加了const就不可修改 cout << "val = " << val << endl; } int main() { //常量引用 //使用场景:用来修饰形参,防止误操作 //int a = 10; //加上const之后, 编译器将代码修改 int temp = 10; const int& ref= temp const int& ref = 10; //引用必须引一个合法的内存空间 //ret = 20; //加入const之后变为只读,不可修改 int a = 100; showValue(a); //a起别名为val cout << "a = " << a << endl; system("pause"); return 0; }
val = 100 a = 100 请按任意键继续. . .
3 函数提高
3.1 函数默认参数
- 在C++中,函数的形参列表中的形参是可以有默认值的
- 语法:
返回值类型 函数名 (参数=默认值){ }
#include<iostream> using namespace std; //函数默认参数 int func(int a, int b, int c) { return a + b + c; } //如果我们自己传入数据,就用自己的数; 没有则用默认值 int func1(int a, int b=20, int c=30) //默认参数 { return a + b + c; } //注意事项 //1、如果某个位置已经有了默认参数,那么从这个位置之后,从左到右必须有默认值 //2、如果函数声明有默认参数,函数实现就不能有默认参数 int func2(int a = 10, int b = 10, int c = 10); //函数声明 int func2(int a, int b, int c) //函数实现 //声明和实现只能有一个有默认参数 { return a + b + c; } int main() { cout << func(10, 20, 30) << endl; cout << func1(20) << endl; cout << func1(10, 10) << endl; cout << func2() << endl; system("pause"); return 0; }
60 70 50 30 请按任意键继续. . .
3.2 函数占位参数
- C++中函数的形参列表中可以有占位参数,用来做占位,调用函数时必须填补该位置。
- 语法:
返回值类型 函数名 (数据类型){}
在现阶段函数的占位参数存在意义不大,但是后面的课程中会用到该作用
#include<iostream> using namespace std; //占位参数 //返回值类型 函数名(数据类型) {} //目前阶段的占位参数还用不到,后面的课程中会用到 //占位参数 还可以有默认参数 void func(int a, int=10) { cout << "this is func" << endl; } int main() { func(10, 10); system("pause"); return 0; }
this is func 请按任意键继续. . .
3.3 函数重载
3.3.1 函数重载概述
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同 或者 个数不同 或者 顺序不同
注意:函数的返回值不可以作为函数重载的条件
#include<iostream> using namespace std; //函数重载 //可以让函数名相同,提高复用性 //函数重载的满足条件 //1、同一个作用域下 //2、函数名称相同 //3、函数的参数类型不同 或 个数不同 或 顺序不同 void func() { cout << "func调用" << endl; } void func(int a) { cout << "func(int a)的调用!" << endl; } void func(double a) //参数类型不同 { cout << "func(double a)的调用!" << endl; } void func(int a, double b) //参数个数不同 { cout << "func(int a, double b)的调用!" << endl; } void func(double a, int b) //参数顺序不同 { cout << "func(double a, int b)的调用!" << endl; } //注意事项 //函数的返回值不可以作为函数重载的条件 //int func(double a, int b) //参数顺序不同 //{ // cout << "func(double a, int b)的调用!" << endl; //} int main() { func(); func(10); func(3.14); func(1, 1.1); func(1.1, 1); system("pause"); return 0; }
func调用 func(int a)的调用! func(double a)的调用! func(int a, double b)的调用! func(double a, int b)的调用! 请按任意键继续. . .
3.3.2 函数重载注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
#include<iostream> using namespace std; //函数重载的注意事项 //1、引用作为重载的条件 void func(int& a) { cout << "func(int& a)调用" << endl; } void func(const int& a) //类型不同 { cout << "func(const int& a)调用" << endl; } //2、函数重载碰到默认参数 void func2(int a) { cout << "func2(int a)的调用" << endl; } //void func2(int a, int b=10) //{ // cout << "func2(int a, int b=10)的调用" << endl; //} int main() { int a = 10; func(a); //调用没有const的函数 func(10); //调用有const的函数 func2(10); //当函数重载碰到默认参数,出现二义性,报错,尽量避免这种情况 system("pause"); return 0; }
func(int& a)调用 func(const int& a)调用 func2(int a)的调用 请按任意键继续. . .
4 类和对象
- C++面向对象的三大特性为:封装、继承、多态
- C++认为万事都皆为对象,对象上有其属性和行为
例如:
人可以作为对象,属性有姓名、年龄、身高、体重…,行为有走、跳、跑、吃饭…
车也可以作为对象,属性有轮胎、方向盘、车灯…,行为有载人、放音乐、放空调…
具有相同性质的对象,我们可抽象为类,人属于人类,车属于车类
4.1 封装
4.1.1 封装的意义
封装是C++面向对象的三大特性之一
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装的意义一:
- 在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{访问权限:属性/行为};
#include<iostream> using namespace std; //设计一个圆类,求圆的周长 //圆求周长的公式:2*pi*r const double PI = 3.14; 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; }
圆的周长为:62.8 请按任意键继续. . .
示例2:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
#include<iostream> using namespace std; #include<string> //设计一个学生类,属性有姓名和学号 //可以给姓名和学号赋值,可以显示学生的姓名和学号 //设计学生类 class Student { //类中的属性和行为 我们统一称为 成员 //属性: 成员属性 成员变量 //行为 成员函数 成员方法 //访问权限 //公共权限 public: //属性 //姓名 string name; //学号 string id; //行为 //显示姓名和学号 void showStudent() { cout << "姓名:" << name << endl; cout << "学号:" << id << endl; } void setName(string a_name) //行为给属性赋值 { name = a_name; } void setID(string a_id) { id = a_id; } }; int main() { Student std1; //实例化 std1.name = "zzz"; //赋值 std1.id = "123456"; std1.showStudent(); //Student std2; std1.setName("张三"); std1.setID("222222"); std1.showStudent(); system("pause"); return 0; }
封装意义二:
- 类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
- 1、public :公共权限
- 2、protected:保护权限
- 3、private:私有权限
#include<iostream> using namespace std; //访问权限 : 三种 //1、公共权限 public 成员类内可以访问,类外也可以访问 //2、保护权限 protected 成员类内可以访问 类外不可以访问 子类也可以访问 //3、私有权限 private 成员类内可以访问 类外不可以访问 子类不可以访问 class Person { public: //公共权限 string m_Name; void func() { m_Name = "张三"; //类内可以访问 m_Car = "拖拉机"; m_Password = 123456; } void show() { cout << "姓名:" << m_Name << endl; cout << "车:" << m_Car << endl; cout << "密码:" << m_Password << endl; } protected: //保护权限 string m_Car; private: //私有权限 int m_Password; }; int main() { Person p1; p1.func(); p1.show(); cout << "修改之后------------" << endl; p1.m_Name = "李四"; cout << "姓名:" << p1.m_Name << endl; //下面都报错,不能访问 //cout << "车:" << p1.m_Car << endl; //p1.m_Car = "奔驰"; //p1.m_Password = 123; system("pause"); return 0; }