C++核心编程
1.内存分区模型
c++执行程序时,将内存大方向划分为4个区域
- 代码区:存放函数的二进制代码,由操作系统进行管理
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数参数值,局部变量等。
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
内存四区的意义:不同区域存放的数据赋予不同的生命周期,给予更大的灵活编程。
1.1 程序运行之前
在程序编译之后,生成exe文件,在运行之前程序分两个区域
代码区:
- 存放cpu执行的机器指令(实际就是你写的代码翻译成机器语言)
- 代码区是共享的:目的是对于哪些频繁执行的程序,只需要在内存中有一份代码即可。
- 代码区是只读的:防止意外修改指令。
全局区:
- 全局变量和静态变量
- 还包含常量区,字符串常量和其他常量(const修饰的常量)也存放在这
- 该区的数据在程序执行结束之后由操作系统释放
#include<iostream>
using namespace std;
//全局变量
int g_a = 10;
int g_b = 10;
int c_g_a = 10;//const修饰的全局常量
int c_g_b = 10;//const修饰的全局常量
int main() {
//全局区
//全局变量、静态变量、常量
//创建普通局部变量
int a = 10;
int b = 10;
cout << "局部变量a的地址为:" << (int)&a << endl;
cout << "局部变量b的地址为:" << (int)&b << endl;
cout << "全局变量a的地址为:" << (int)&g_a << endl;
cout << "全局变量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 << "const修饰的全局常量c_g_a地址为:" << (int)&c_g_a << endl;
cout << "const修饰的全局常量c_g_b地址为:" << (int)&c_g_b << endl;
const int c_l_a = 10;//l:local
const int c_l_b = 10;
cout << "const修饰的局部常量c_l_a地址为:" << (int)&c_l_a << endl;
cout << "const修饰的局部常量c_l_b地址为:" << (int)&c_l_b << endl;
system("pause");
return 0;
}
结果:
总结:
- c++中在程序运行之前分为全局区和代码区
- 代码区特点是共享和只读
- 全局区中存放的是全局变量、静态变量、常量
- 常量区中存放const修饰的全局变量和字符串常量
1.2 程序运行之后
栈区:
由编译器自动分配释放,存放函数参数值,局部变量等。
注意:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。
#include<iostream>
using namespace std;
//栈区的注意事项--不要返回局部变量的地址
//栈区的数据由编译器管理开辟和释放
int* func() {//如果有形参,形参也会存放在栈区
int a = 10;//局部变量 存放在栈区,栈区的数据在函数执行完成之后自动释放
return &a;//返回局部变量的地址
}
int main() {
int *p = func();//利用指针接收func函数的返回值
cout << *p << endl;//第一次可以打印正确数字因为编译器做了保留
cout << *p << endl;//第二次打印会是乱码,因为数据不在保留了
system("pause");
system("pause");
return 0;
}
输出:
10
2064763376//乱码
堆区:
由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
在c++中主要利用new在堆区中开辟内存
#include<iostream>
using namespace std;
int* func1() {
//利用new关键字把数据开辟到堆区
//指针本质也是局部变量,放在栈上,指针保存的数据放在堆区
int* p = new int(10);
return p;
}
int main() {
//在堆区开辟数据
int* p = func1();
cout << *p << endl;
system("pause");
return 0;
}
输出:
10
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;//解引用输出
//delete p;
//cout << *p << endl;//释放了内内存再区访问会出现非法操作异常
}
//2.在堆区利用new开辟数组
void test02() {
//在堆区创建10个整型数据的数组
int *arr=new int[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;
}
//释放堆区数组
delete[]arr;//释放数组的时候要加一个[]
}
int main() {
test01();
test02();
system("pause");
return 0;
}
2.引用
2.1 引用的基本使用
作用:给变量起别名
语法:数据类型 &别名 = 原名
#include<iostream>
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;
}
输出:
a=10
b=10
a=100
b=100
2.2 引用注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
#include<iostream>
using namespace std;
int main() {
int a = 10;
//1.引用必须初始化
//int& b;报错
int& b = a;
//2.引用初始化后不可以改变
int c = 20;
b = c;//这是赋值操作,不是更改引用,相当于把a和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 mySwap01(int a, int b){
int temp = a;
a = b;
b = temp;
//cout << "swap01 a=" << a << endl;//形参会改变
//cout << "swap01 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);
cout << "地址传递a=" << a << endl;
cout << "地址传递b=" << b << endl;
mySwap03(a, b);//引用传递,形参也会修饰实参
cout << "引用传递a=" << a << endl;//这地方要注意一下,因为你上面是连续调用,mySwap02已经把实参改了,
//所以这里又给改回来了,别看不懂
cout << "引用传递b=" << b << endl;
system("pause");
return 0;
}
输出:
值传递a=10
值传递b=20
地址传递a=20
地址传递b=10
引用传递a=10
引用传递b=20//这地方别看不懂,相当于是交换的a=20和b=10
2.4 引用做函数返回值
注意:不要返回局部变量的引用
#include<iostream>
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();//这里是用ref这个别名去接收了test01返回的引用值
//cout << "ref=" << ref << endl;//第一次编译器保留了
//cout << "ref=" << ref << endl;//第二次输出就是乱码了,a的内存已经释放了
int& ref2 = test02();
cout << "ref2=" << ref2 << endl;// 10
cout << "ref2=" << ref2 << endl;// 10这时候没问题
test02() = 1000;//函数的返回值是引用,这个函数调用可以作为左值,因为这个函数返回的就是a的一个引用,就相当于把a返回了;
//那就相当于再赋值1000给a
cout << "ref2=" << ref2 << endl;//这里ref2是别名去访问a也是输出a的值,返回1000
cout << "ref2=" << ref2 << endl;//返回1000
system("pause");
return 0;
}
2.5 引用的本质
本质:在c++内部实现是一个指针常量(指向不能改)
#include<iostream>
using namespace std;
void func(int& ref) {
ref = 100;//ref是引用,转换为*ref=100;
}
int main() {
int a = 10;
int& ref = a;//执行这行代码的时候会自动转换成 int* const ref = &a,指针常量所以这个引用是不能改的
ref = 20;//内部发现ref是引用,自动转换成*ref=20;
cout << "a:" << a << endl;
cout << "ref:" << ref << endl;
func(a);
system("pause");
return 0;
}
2.6 常量引用
作用:主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参。
#include<iostream>
using namespace std;
//打印数据函数
void showValue(const int &val) {
//val = 1000;//这里就不能改了,相应的外部的实参就不会变
cout << "val=" << val << endl;
}
int main() {
//常量引用
//用来修饰形参,防止误操作
//int a = 10;
//int& ref = 10;//直接引用一个10是不行的,引用必须引用一个合法的内存空间比如堆区栈区,10在常量区不能引用
//const int& ref = 10;//在前面加一个const这样就可以了,编译器修改为int temp=10;const int &ref =temp;
//ref = 20;//直接报错不可修改
int a = 100;
showValue(a);
system("pause");
return 0;
}
3 函数提高
3.1 函数默认参数
在c++中,函数的形参列表中的形参也可以有默认值
语法:函数返回值类型 函数名 (参数=默认值){}
#include<iostream>
using namespace std;
//函数默认参数
int func(int a=10,int b=20,int c=30) {
return a + b + c;
}
//注意事项:
//1.如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值。
//2.如果函数的声明有了默认参数,那么函数的实现就不能有默认参数了,声明和实现只能有一个存在默认参数
int func1(int a = 10, int b = 20);
int func1(int a = 10, int b = 20) {//这时候运行的时候会报错
return a + b;
}
int main() {
cout << func(1, 2, 3) << endl;//传了之后用传的,没传用默认的
system("pause");
return 0;
}
3.2 函数占位参数
c++函数的形参列表里面可以有占位参数,用来做占位,调用函数的时候必须填补这个位置
语法:函数返回值类型 函数名(数据类型){}
#include<iostream>
using namespace std;
//占位参数
//占位参数也可以有默认参数
void func(int a,int =10) {//这个int数据类型就是占位,然后给了个默认值10
cout << "this is func" << endl;
}
int main() {
func(10,10);
system("pause");
return 0;
}
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;
}
int main() {
func();
func(10);
system("pause");
return 0;
}
注意:函数的返回值是不可以作为函数重载的条件的。
3.3.2 函数重载的注意事项
- 引用作为重载条件
- 函数重载碰到函数默认参数
#include<iostream>
using namespace std;
//函数重载的注意事项
//1.引用作为重载条件
void func(int& a) {//int &a=10;不合法
cout << "func(int &a)调用" << endl;
}
void func(const int& a) {//const int& a=10;合法
cout << "func(const int &a)调用" << endl;
}
//2.函数重载碰到默认参数
void func2(int a,int b=10)
{
cout << "func2(int a,int b)的调用" << endl;
}
void func2(int a)
{
cout << "func2(int a)的调用" << endl;
}
int main() {
int a = 10;
func(a);//这时候调用的是第一个,因为a本身是一个变量可读可写,而const是只读状态所以不能走这个
func(10);//这时候调用的是下面那个,如果走上面那个相当于int &a=10;不合法
func2(10)//func2的这两个函数是都可以调用的,出现歧义报错;
system("pause");
return 0;
}
4 类和对象
c++面向对象三大特性:封装、继承、多态
c++认为万事万物都是对象,对象上有其属性和行为
4.1 封装
4.1.1 封装的意义
封装的意义:
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
封装意义一:
- 在设计类的时候,属性和行为写在一起,表现事物
语法:class 类名{ 访问权限: 属性/行为};
案例1:设计一个圆类,来求圆的周长
#include<iostream>
using namespace std;
//设计一个圆类,来求圆的周长 公式:2*pi*半径
//圆周率
const double PI = 3.14;
class Circle
{
//访问权限
public://公共权限
//属性
int m_r;//半径
//行为
//获取圆的周长
double caculateZC() {
return 2 * PI * m_r;
}
};
int main() {
//通过圆类创建具体的圆(对象)
//实例化(通过一个类创建一个对象的过程)
Circle c1;
//给圆对象的属性赋值
c1.m_r = 10;
cout << "圆的周长为:" << c1.caculateZC() << endl;
system("pause");
return 0;
}
示例2:设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号
一些术语:
-
类中的属性和行为统称为 成员
-
属性 成员属性 成员变量
-
行为 成员函数 成员方法
#include<iostream>
using namespace std;
class Student
{
public:
string m_Name;//姓名
string m_Id;//学号
void printinfo() {
cout << "学生姓名:" << m_Name << endl;
cout << "学生学号:" << m_Id << endl;
}
//给姓名赋值
void steName(string name) {
m_Name = name;
}
};
int main() {
Student s1;
s1.m_Name = "张三";
s1.m_Id = "123451626";
s1.printinfo();
Student s2;
s2.steName("张三");//通过类的行为进行赋值操作
s2.m_Id = "151651";
system("pause");
return 0;
}
封装的意义二:
在设计类的时候,可以把属性和行为放在不同的权限下,加以控制。
访问权限有3种:
- public 公共权限(类内可以访问,类外也可以访问)
- protected 保护权限(类内可以访问,类外不可以访问)子类可以访问父类的保护权限
- private 私有权限(类内可以访问,类外不可以访问)子类不能访问父类的私有权限
#include<iostream>
using namespace std;
//访问权限
//1. public 公共权限
//2. protected 保护权限
//3. private 私有权限
class Person
{
//公共权限
public:
string m_Name;//姓名
protected:
string m_Car;//汽车
private:
int m_Password;//银行卡密码
public:
void func() {
m_Name = "张三";//类内可以访问
m_Car = "拖拉机";//类内可以访问
m_Password = 123456;//类内可以访问
}
};
int main() {
Person p1;
p1.m_Name = "李四";
//p1.m_Car = "奔驰";//无法访问直接报错
//p1.m_Password=123455;//类外无法访问
system("pause");
return 0;
}
4.1.2 struct和class区别
c++中 struct和class唯一的区别在于默认访问权限不同
区别:
- struct默认权限为公共
- class默认权限为私有
#include<iostream>
using namespace std;
class C1
{
int m_A;//默认权限是私有
};
struct C2
{
int m_A;//默认权限是公共
};
int main() {
//struct 和class的区别
//struct默认权限为公共
//class默认权限为私有
C1 c1;
//c1.m_A = 100;//报错
C2 c2;
c2.m_A = 100;//不会报错
system("pause");
return 0;
}
4.1.3 成员属性设置为私有
优点1:将成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
#include<iostream>
using namespace std;
//成员属性设置为私有
//1.可以自己控制读写的权限
//2.对于写可以检测数据的有效性
//设计人
class Person {
public:
//写姓名
void steName(string name) {
m_Name = name;
}
//获取姓名
string getName() {
return m_Name;
}
//获取年龄 可读可写 如果想修改(年龄的范围必须是0-150之间)
int getAge() {
//m_Age = 0;//直接初始化了
return m_Age;
}
//设置年龄
void steAge(int age) {
if (age<0||age>150)
{
cout << "输入有误!" << endl;
return ;
}
else
{
m_Age = age;
}
}
void setHight(int hight) {
m_hight = hight;
}
private:
//姓名 给出一个可读可写的权限
string m_Name;
//年龄 给出一个只读权限
int m_Age;
//身高 给出一个只写的权限
int m_hight;
};
int main() {
Person p;
p.steName("张三");
cout << "姓名为:" << p.getName() << endl;
p.steAge(1000);
cout << "年龄为:" << p.getAge() << endl;
p.setHight(178);
system("pause");
return 0;
}
案例练习:设计立方体类)(Cube)
求出立方体的面积和体积
分别于全局函数和成员函数判断两个立方体是否相等
#include<iostream>
using namespace std;
//立方体类设计
//1.创建立方体类
//2.设计属性和行为
//3.获取立方体面积和体积
//4.分别利用全局函数和成员函数判断两个立方体是否相等
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 caculateV() {
return m_L * m_W * m_H;
}
//利用成员函数判断两个立方体是否相等
bool isSameByClass(Cube &c) {
if (m_L == c.getL() && m_W == c.getW() && m_H == 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.caculateV() << endl;
//创建第二个立方体
Cube c2;
c2.setL(10);
c2.setW(10);
c2.setH(100);
//全局函数判断
bool ret = isSame(c1, c2);
if (ret)
{
cout << "c1和c2是相等的" << endl;
}
else
{
cout << "c1和c2是不相等的" << endl;
}
//利用成员函数判断
ret = c1.isSameByClass(c2);
if (ret)
{
cout << "成员函数判断c1和c2是相等的" << endl;
}
else
{
cout << "成员函数判断c1和c2是不相等的" << endl;
}
system("pause");
return 0;
}
22