C++之核心编程
1 内存分区模型
c++程序执行时,将内存大小方向划分为4个区域:
- 代码区:存放函数体的二进制代码,由操作系统进行管理(所有代码)
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配和释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回放
内存四区意义:
不同区域存放的数量,赋予不同的生命周期,给我们更大的灵活编程
1.1 程序运行前
程序编译后,生成了exe可执行程序,未执行程序前后分为两个区域
代码区:
存放cpu执行的机器指令
代码区是共享的
代码区是只读的
全局区:
全局变量和静态变量存放在此
全局区包含了常量区、字符常量区和其他常量区
(该区域的数据在程序结束后由此操作系统释放)
1.2 程序运行后
栈区:
由编译器自动分配释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
堆区:
由程序员分配和释放,若程序员不释放,程序结束时由操作系统回放
在C++中主要利用new在堆区开辟内存
1.3 new 操作符
C++中利用new操作符在堆区开服数据
堆区开辟数据,由程序员手动开辟,手动释放,释放利用操作符delete
语法: new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
2 引用
等价于指针作用,比指针简单
2.1 引用的基本使用
作用:给变量起别名
语法:数据类型 &别名 = 原名
int main()
{
int a = 10;
int& b = a;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
b = 50;
cout << "a=" << a << endl;
cout << "b=" << b << endl;
system("pause");
return 0;
}
2.2 引用注意事项
- 引用必须初始化
- 引用在初始化后,不可改变
2.3 引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
2.4 引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
2.5 引用的本质
本质:引用的本质在C++内部实现是一个指针常量
int a = 10;
//int * const ref =&a;指针常量是指针指向不可改,也说明为什么引用不可更改
int& ref = a;
ref = 20;//内部发现ref是引用,自动转换为*ref = 20 ;
2.6 常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
3 函数提高
3.1 函数默认参数
3.2 函数占位参数
3.3 函数重载
可使函数名相同,提高复用性
3.3.1 函数重载概述
//满足条件
//1.同一个作用域(全局)
//2.函数名称相同
//3.函数参数类型不同,或者个数不同,或者顺序不同
//函数的返回值不可以作为函数重载的条件
void func(int a, double b)
{
}
int func(int a, double b)
{
}
3.3.2 函数重载注意事项
- 引用作为重载条件
void func(int &a)
{
}
//int a=10,调用func(a)
int func(const int a)
{
}
//int a=10,调用func(10)
- 函数重载碰到函数默认参数
void func(int a,int b=5)
{
}
int func(int a)
{
}
func(10)//调用出现二义性,所以尽量避免函数默认参数
4 类和对象
4.1 封装
4.1.1 封装的意义
设计学生类:
#include<iostream>
using namespace std;
#include<string>
class stu
{
//访问权限
// 公共权限
public:
//属性 --->成员属性 成员变量
//姓名
string N_name;
//学号
string N_number;
//行为 --->成员函数 成员方法
//显示姓名和学号
void printname()
{
cout << "姓名:" << N_name << "\t" << "学号:" << N_number << endl;
}
//赋值
void setname(string name)
{
N_name = name;
}
void setnumber(string number)
{
N_number = number;
}
};
int main()
{
stu s1;
//赋值方式1:
//s1.N_name = "王飞鸿";
//s1.N_number = "20180550";
//赋值方式2:
s1.setname("王飞鸿");
s1.setnumber("20180551");
s1.printname();
stu s2;
s2.setname("王大侠");
s2.setnumber("12345678");
s2.printname();
system("pause");
return 0;
}
访问权限
//public 公共权限 类内可以访问,类外也可以访问
//protected 保护权限 类内可以访问,类外不可以访问 儿子可以访问父亲的保护内容
//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()
{
class Person p1;
p1.m_name = "李四";
//p1.m_car = "摩托";
//p1.m_password = 654321;
system("pause");
return 0;
}
4.1.2 struct 和class区别
//class和struct的区别
//struct 默认权限是 公共 public
//class 默认权限是 私有 private
4.1.3成员属性设置为私有
//1、可以自己控制读写权限
//2、对于写可以检测数据的有效性
class Person
{
public:
//设置姓名
void setName(string name)
{
m_name = name;
}
//获取姓名
string getName()
{
return m_name;
}
//获取年龄
int getAge()
{
//m_age = 0;//初始化为0岁
return m_age;
}
void setAge(int age) //2、对于写可以检测数据的有效性
{
if (age < 0 || age>150)
{
cout << "你对人的年龄有误解" << endl;
return;
}
m_age = age;
}
//设置密码
void setPassword(string password)
{
m_password = password;
}
private:
//姓名 可读可写
string m_name;
//年龄 只读
int m_age;
//密码 只写
string m_password;
};
int main()
{
Person p;
p.setName("jhon");
cout << "姓名:"<< p.getName() << endl;
//p.m_name = 18;无法直接访问
//p.getAge(18);只读状态,不能更改
p.setPassword("jhon");//只能写
p.setAge(180);
cout << "年龄:"<< p.getAge() << endl;//若p.setAge(int age)中输入<0||>150,输出年龄会乱码
system("pause");
return 0;
}
案例:立方体类
class cube
{
public:
//行为 //1
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 caculateS()//获取面积
{
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;
}
//2.2成员函数判断立方体
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;//高
};
//2.1全局函数判断立方体是否相等 //跳出class
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()
{
//创建立方体对象c1
cube c1;
c1.setL(10);
c1.setW(10);
c1.setH(10);
cout << "该立方体面积:"<<c1.caculateS() << endl;
cout << "该立方体体积:"<<c1.caculateV() << endl;
//创建第二个立方体对象c2
cube c2;
c2.setL(10);
c2.setW(10);
c2.setH(10);
//全局函数判断
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;
}
4.2 对象的初始化和清理
4.2.1 构造和析构函数
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无法手动调用
- 析构函数:主要作用在于销毁前系统自动调用,执行一些清理工作
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无法手动调用,而且只会调用一次
析构函数语法:~类名(){}
1.析构函数,没有返回值,也不用void
2.函数名称与类名相同,在名称前加上符号~
3.析构函数不可以有参数,因此不可以重载
4.程序在对象销毁前会自动调用析构,无需手动调用,而且只会调用一次
4.2.2 构造函数的分类及调用
#include<iostream>
using namespace std;
class person
{
public:
//1.构造函数的分类
//按参数分类 有参构造 无参构造(默认构造)
//按类型构造 普通构造 拷贝构造
//构造
person()
{
cout << "构造函数中无参构造person的调用" << endl;
}
person(int a )
{
age = a;
cout << "构造函数中有参构造person的调用" << endl;
}
person(const person &p)
{
age = p.age;
cout << "构造函数中拷贝构造person的调用" << endl;
}
//析构
~person()
{
cout << "析构函数~person的调用" << endl;
}
int age;
};
//2.构造函数的调用
void test01()
{
//2.1 括号法
person p1;
person p2(5);
person p3(p2);
//注意事项1:person p1不能写成person p1(),即不能加括号,因为编译器会误认为是声明
//2.2 显示法
person p4 = person(5);
person p5 = person(p4);
//person(5);//单独写是匿名对象,当前行结束后马上回收,开始析构
//注意事项2:不要利用拷贝函数匿名初始化对象,编译器会认为person (p3)===person p3(声明) 则发生"重定义"
//person(p2);
//2.3 隐式转换法
person p6 =8; //person p6 =person p(8)
person p7 = p6;//person p7 =person (p6)
}
int main()
{
test01(); //控制窗都显示
//person p; //区别构造和析构 (析构在该main函数执行完后释放~person())
system("pause");
return 0;
}
4.2.3 拷贝函数调用时机
三种情况:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传递
- 以值方式返回局部对象
4.2.4 构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则:
- 若用户定义有参构造,C++不再提供默认无参构造,但会提供默认拷贝构造
- 若用户定义拷贝构造,C++不再提供其他构造函数
4.2.5 深拷贝与浅拷贝
- 浅拷贝:简单的赋值拷贝操作
- 深拷贝:在堆区重新申请空间进行拷贝
拷贝构造函数//new新建内存
person(const person& p)
{
cout << "拷贝构造函数:" << endl;
//如果不利用深拷贝在堆区新建内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);
}
析构函数//释放new新建的内存
~person()
{
cout << "析构函数" << endl;
if (m_height != NULL)
{
delete m_height;
}
}
4.2.6 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2);…{}
class person
{
public:
person(int a, int b, int c) :m_A(a), m_B(b), m_C(c){
}
int m_A;
int m_B;
int m_C;
};
void print()
{
person p(10, 20, 30);
cout << "m_A=" << p.m_A << endl;
cout << "m_B=" << p.m_B << endl;
cout << "m_C=" << p.m_C << endl;
}
int main()
{
print();
system("pause");
return 0;
}
4.2.7 类对象作为类成员
- 当其他对象作为本类成员,构造时先构造类对象,再构造自身
- 析构的顺序与构造相反
//手机类
class Phone
{
public:
Phone(string name)
{
m_Pname = name;
}
string m_Pname;
};
//人类
class Person
{
public:
Person(string name,string pname):m_Name(name), m_Phone(pname)
{
}
string m_Name;
Phone m_Phone;
};
void test01()
{
Person p("jack", "小米");
cout << p.m_Name << "有" << p.m_Phone.m_Pname << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
4.2.8 静态成员函数
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
-
静态成员变量
1.所有对象共享同一份数据
2.在编译阶段分配内存
3.类内声明,类外初始化 -
静态成员函数
1.所有对象共享同一个函数
2.静态成员函数只能访问静态成员变量
两种访问方式:
1.通过对象访问
person P;
p.func();
2.通过类名访问
person::func();
4.3 对象模型和this指针
4.3.1 成员变量和成员函数分开储存
只有非静态成员变量才属于类的对象上
4.3.2 this指针概念
this指针指向 被调用的成员函数 所属对象
用途:
- 当形参和成员变量同名时,可用this指针来区分 this–>
- 在类的非静态成员函数中返回对象本身(返回对象而非值),可用return *this
4.3.3 空指针访问成员函数
空指针可以访问成员,空指针不能访问属性
用以下代码避免程序崩溃
if(tihis == NULL )
{
return;
}
this指针的本质:指针常量 指针的指向不可以修改
4.3.4 const修饰成员函数
常函数:
void func() const
- 成员函数后加const后称为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以已修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
4.4 友元
好基友(friend)可以访问private
4.4.1 全局函数做友元
friend void func(Person * p1);
4.4.2 类做友元
friend class GoodGay;
4.4.3 成员函数做友元
friend void GoodGay::visit();
4.5 运算符重载
4.6 继承
4.6.1 基本语法
用途:减少重复代码
语法:
class 子类:继承方式 父类;
class A :public B;
A称为子类也称为派生类
B称为父类也称为基类
//公共页面(Java、Python、C++...)
class BasePage
{
(公共的内容)
}
//java页面
class Java:public BasePage//语法
{
public:
(写不一样的)
}
4.6.2 继承方式
分三种:
- 公共继承
- 保护继承
- 私有继承
4.6.3 继承中的对象模型
4.6.4 继承中构造和析构顺序
4.6.5 继承同名成员处理方式
4.6.6 继承同名静态成员处理
4.6.7 多继承语法
4.6.8 菱形继承
4.7 多态
4.7.1 多态的基本概念
多态是C++面向对象三大特性之一
多态分两类:
- 静态多态:函数重载 和 运算符重载属于静态多态,服用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别: - 静态多态的函数地址早绑定- 编译阶段确定函数地址
- 动态多态的函数地址晚绑定- 运行阶段确定函数地址
动态多态满足条件:
1.有继承关系
2.子类重写父类中的虚函数–>添加 virtual
重写:函数返回类型 函数名 参数列表 完全相同
4.7.2 多态案例1计算器
4.7.3 纯虚数和抽象类
4.7.4 多态案例2制作饮品
4.7.5 虚析构和纯虚析构
4.7.6 多态案例3电脑组装
5 文件操作
未完,待续…