C++学习中
内存分区模型
C++程序在执行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
内存四区意义:
不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程
程序运行前
在程序编译后,生成了exe可执行程序,未执行该程序前分为两个区域
代码区:
存放 CPU 执行的机器指令
代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
全局变量和静态变量存放在此.
全局区还包含了常量区, 字符串常量和其他常量也存放在此.
该区域的数据在程序结束后由操作系统释放.
示例:
C++
//全局变量
int g_a = 10;
int g_b = 10;
//全局常量
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 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;
cout << "字符串常量地址为: " << (int)&"hello world1" << endl;
cout << "全局常量c_g_a地址为: " << (int)&c_g_a << endl;
cout << "全局常量c_g_b地址为: " << (int)&c_g_b << endl;
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;
}
打印结果:
总结:
- C++中在程序运行前分为全局区和代码区
- 代码区特点是共享和只读
- 全局区中存放全局变量、静态变量、常量
- 常量区中存放 const修饰的全局常量 和 字符串常量
程序运行后
栈区:
由编译器自动分配释放, 存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
示例:
C++
int * func()
{
int a = 10;
return &a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
堆区:
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
在C++中主要利用new在堆区开辟内存
示例:
C++
int* func()
{
int* a = new int(10);
return a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
system("pause");
return 0;
}
总结:
堆区数据由程序员管理开辟和释放
堆区数据利用new关键字进行开辟内存
new操作符
C++中利用new操作符在堆区开辟数据
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete
语法: new 数据类型
利用new创建的数据,会返回该数据对应的类型的指针
示例1: 基本语法
C++
int* func()
{
int* a = new int(10);
return a;
}
int main() {
int *p = func();
cout << *p << endl;
cout << *p << endl;
//利用delete释放堆区数据
delete p;
//cout << *p << endl; //报错,释放的空间不可访问
system("pause");
return 0;
}
示例2:开辟数组
C++
//堆区开辟数组
int main() {
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 后加 []
delete[] arr;
system("pause");
return 0;
}
引用
引用的基本使用
**作用: **给变量起别名
语法: 数据类型 &别名 = 原名
示例:
C++
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;
}
引用注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
示例:
C++
int main() {
int a = 10;
int b = 20;
//int &c; //错误,引用必须初始化
int &c = a; //一旦初始化后,就不可以更改
c = b; //这是赋值操作,不是更改引用
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
system("pause");
return 0;
}
引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
示例:
C++
//1. 值传递
void mySwap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//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 << " b:" << b << endl;
mySwap02(&a, &b);
cout << "a:" << a << " b:" << b << endl;
mySwap03(a, b);
cout << "a:" << a << " b:" << b << endl;
system("pause");
return 0;
}
总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
示例:
C++
//返回局部变量引用
int& test01() {
int a = 10; //局部变量
return a;
}
//返回静态变量引用
int& test02() {
static int a = 20;
return a;
}
int main() {
//不能返回局部变量的引用
int& ref = test01();
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
//如果函数做左值,那么必须返回引用
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++内部实现是一个指针常量.
讲解示例:
C++
//发现是引用,转换为 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);
return 0;
}
结论:C++推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加const修饰形参,防止形参改变实参
示例:
C++
//引用使用的场景,通常用来修饰形参
void showValue(const int& v) {
//v += 10;
cout << v << endl;
}
int main() {
//int& ref = 10; 引用本身需要一个合法的内存空间,因此这行错误
//加入const就可以了,编译器优化代码,int temp = 10; const int& ref = temp;
const int& ref = 10;
//ref = 100; //加入const后不可以修改变量
cout << ref << endl;
//函数中利用常量引用防止误操作修改实参
int a = 10;
showValue(a);
system("pause");
return 0;
}
函数提高
函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的。
语法: 返回值类型 函数名 (参数= 默认值){}
示例:
C++
int func(int a, int b = 10, int c = 10) {
return a + b + c;
}
//1. 如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
//2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
return a + b;
}
函数占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
语法:返回值类型 函数名 (数据类型){}
//第二个int表示占位
void func(int a, int){
cout << "this is func";
}
//占位参数还可以有默认参数
void func(int a, int = 10){
}
函数重载
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名相同
- 函数参数类型不同或者个数不同或者顺序不同
函数的返回值不可以作为函数重载的条件
void func(){
}
void func(int a){
}
//顺序不同
void func(int a, double b){
}
void func(double a,int b){
}
//引用作为重载的条件
void fun(int &a){
}
void fun(const int &a){
}
int a = 10;
fun(a); //用第一个
fun(10);//用第二个
//函数重载碰到默认函数
void func(int a, int b = 10){
}
void func(int a){
}
func(2);//错误,出现二义性
类和对象
C++面向对象的三大特性为:封装、继承、多态
C++认为万事万物皆为对象,对象上有其属性和行为
具有相同性质的对象,我们可以抽象为类。
封装
封装是C++面向对象三大特性之一。
封装的意义:
-
将属性和行为作为一个整体,表现生活中的事物
在设计类的时候,属性和行为写在一起,表示事物
语法:
class 类名{ 访问权限:属性/行为};#include<iostream> #define PI 3.14 using namespace std; class Cricle{ public: //属性 int m_r; //行为 double calculateZC(){ return 2 * PI * m_r; } }; int main(){ //实例化对象 Cricle c1; //用.访问对象里面的属性或行为 c1.m_r = 10; cout << "圆的周长为:" << c1.calculateZC() << endl; return 0; } -
将属性和行为加以权限控制
类在设计是,可以把属性和行为放在不同的权限下,加以控制。
访问权限有三种:
-
public 公共权限
类内类外都可以访问
-
protected 保护权限
类内可以访问,类外不可以,子类可以访问父类
-
private 私有权限
类内可以访问,类外不可以,子类不能访问父类
-
struct 和 class的区别
struct默认权限是public
class默认权限是private
将成员属性设置为私有
- 优点1:将成员属性设置为私有,可以自己控制读写权限
- 优点2:对于写权限,我们可以检测数据的有效性
对象的初始化和清理
构造函数和析构函数
C++利用构造函数和析构函数解决对象的初始化和清理问题,这两个函数会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要求我们做的事情,因此如果我们不提供构造函数和析构函数,编译器会提供编译器自带的构造函数和析构函数。
-
构造函数:只要作用在于创建对象时为对象的成员赋值,构造函数由编译器自动调用,无需手动调用
语法:
类名(){}- 没有返回值也不写void
- 函数名和类名相同
- 可以有参数,因此可以发生重载
- 程序在调用对象时会自动调用构造,无需手动调用,而且只会调用一次
按照类型分类:
-
普通构造函数
-
拷贝构造函数
class person{ public: int age; person(const person &p){ //将P中的拷贝上这个对象上 age = p.age; } }
调用方法:
-
括号法
person p2(10);调用默认构造函数时,不要加(),会被看成是函数声明
-
显示法
person p2 = person(10); person(10);//匿名对象 当前执行结束后,系统会自动回收 //不要用拷贝构造函数初始化匿名对象,person(p3) === person p3 -
隐式转换法
person p4 = 10; // person p4 = person(10);
拷贝构造函数的调用时机:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
构造函数调用时机:
默认情况下,c++编译器至少给一个类添加3个函数
- 默认构造函数
- 默认析构函数
- 默认拷贝构造函数,对属性值进行拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不在提供默认无参构造函数,但是会提供默认拷贝构造函数
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
深拷贝与浅拷贝:
-
深拷贝:在堆区重新申请空间,进行拷贝操作
-
浅拷贝:简单的赋值拷贝操作
如果利用编译器提供的拷贝构造函数,会做浅拷贝操作
-
析构函数:主要作用在于对象销毁前的系统自动调用,执行一些清理工作
语法:
~类名(){}- 没有返回值也不写void
- 函数名和类名相同,前面加上~
- 不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用构造,无需手动调用,而且只会调用一次
初始化列表
作用:C++提供了初始化列表语法,来初始化属性
语法:构造函数() : 属性1(值1),属性2(值2)……{}
class person{
public:
int m_a;
int m_b;
int m_c;
person():m_a(10), m_b(20), m_c(30){
}
person(int a, int b, int c):m_a(a), m_b(b), m_c(c){
}
}
类对象作为类成员
C++中的成员可以是另一个类的对象,我们称该对象为对象成员
class A{
}
class B{
A a;
}
静态成员
静态成员就是在成员变量和成员函数前加关键字static,称为静态变量。
静态成员分为:
-
静态成员变量
-
所有对象共享同一份数据
-
在编译阶段分配内存
-
类内声明,类外初始化
-
静态成员变量不属于某个对象,所有对象都共享同一份数据,因此静态成员变量有两种访问方法
-
通过对象进行访问
-
通过类名进行访问
cout << person::m_a << endl;
#include<iostream> using namespace std; class person{ public: static int m_a; }; int person::m_a = 100; int main(){ person p; cout << p.m_a << endl; return 0; } -
-
-
静态成员函数
-
所有对象共享同一个函数
-
静态成员函数只能访问静态成员变量
class person{ public: //静态成员函数 static void func(){ cout << "static void func调用" << endl; } }; -
静态成员函数有两种访问方法
-
通过对象进行访问
person p; p.func(); -
通过类名进行访问
person::func();
-
-
C++对象模型和this指针
成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储。
只有非静态成员变量才属于类的对象上
-
空对象占用内存空间为1
C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
每个空对象也应该有一个独一无二的内存地址
class person{
public:
int m_a; //属于类的对象上
static int m_b; //不属于类的对象上
void func1(){
}//不属于类的对象上
static void func2(){
}//不属于类的对象上
};
this指针概念
C++中,每个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。
C++通过提供特殊的对象指针,this指针,来解决这一块代码区分是哪个对象调用自己的我呢提,this指针指向被调用的成员函数所属的对象。
this指针是隐含每一个非静态成员函数内的一种指针。
this指针不需要定义,直接使用即可。
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的静态成员函数中返回对象本身,可使用return *this
空指针访问成员函数
C++中空指针也可以调用成员函数的,但是也要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性。
#include<iostream>
using namespace std;
class person{
public:
int m_age;
void ShowClassName(){
cout << "this is person class" << endl;
}
void ShowPersonAge(){
//预防空指针,可以加上一个判断
if(this == NULL){
return;
}
cout << "age = " << m_age << endl;
}
//在m_age里面,默认是this->m_age。不能使用showPersonAge,是因为使用的是空指针。找不到对象
};
int main(){
person *p = NULL;
p->ShowClassName();
p->ShowPersonAge();
return 0;
}
const修饰成员变量
常函数:
- 成员函数后加const后我们称这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依然可以修改
#include<iostream>
using namespace std;
class person(){
public:
int m_a;
mutable int m_b;
//this指针的本质是指针常量,指针的指向是不可以修改的
//在showPerson()后加const,表示const person * const this;
void showPerson() const
{
//错误
m_a = 100;
//正确
m_b = 100;
}
}
int main(){
}
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
#include<iostream>
using namespace std;
class person(){
public:
int m_a;
mutable int m_b;
void showPerson() const
{
m_a = 100;
m_b = 100;
}
void func(){
}
}
int main(){
const person p;//在对象前加const,变为常对象
//错误
p.m_a = 100;
//正确
p.m_b = 100;
//错误
p.func();
//正确
p.showPerson();
}
友元
在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。
友元的目的就是让一个函数或者类访问另一个类中的私有成员
友元的关键字为friend
友元的三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
全局函数做友元
#include<iostream>
#include<string>
using namespace std;
class Building{
//显示goodGuy是友元,可以访问私有函数
friend void goodGuy(Building *building);
public:
string m_SittingRoom; //客厅
Building(){
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
private:
string m_BedRoom;//卧室
};
//全局函数
void goodGuy(Building *building){
cout << "全局函数正在访问:" << building->m_SittingRoom << endl;
cout << "全局函数正在访问:" << building->m_BedRoom << endl;
}
int main(){
Building building;
goodGuy(&building);
return 0;
}
类做友元
#include<iostream>
#include<string>
using namespace std;
class Building{
//定义友元
friend GoodGuy;
public:
string m_SittingRoom; //客厅
Building();
private:
string m_BedRoom;//卧室
};
//类外写成员函数
Building::Building(){
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
class GoodGuy{
public:
Building *building;
//参观函数,访问building中的属性
void visit();
GoodGuy();
};
GoodGuy::GoodGuy(){
building = new Building;
}
void GoodGuy::visit(){
cout << "类正在访问" << building->m_SittingRoom << endl;
cout << "类正在访问" << building->m_BedRoom << endl;
}
int main(){
GoodGuy goodguy;
goodguy.visit();
return 0;
}
成员函数做友元
#include<iostream>
#include<string>
using namespace std;
class Building;
class GoodGuy{
public:
Building *building;
GoodGuy();
//让visit函数可以访问Building中的私有成员
void visit();
//让visit2函数不可以访问Building中的私有成员
void visit2();
};
class Building{
friend void GoodGuy::visit();
public:
string m_SittingRoom; //客厅
Building();
private:
string m_BedRoom;//卧室
};
GoodGuy::GoodGuy(){
building = new Building;
}
void GoodGuy::visit(){
cout << "visit正在访问" << building->m_SittingRoom << endl;
cout << "类正在访问" << building->m_BedRoom << endl;
}
void GoodGuy::visit2(){
cout << "visit2正在访问" << building->m_SittingRoom << endl;
// cout << "类正在访问" << building->m_BedRoom << endl;
}
Building::Building(){
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
int main(){
GoodGuy gg;
gg.visit();
gg.visit2();
return 0;
}
运算符重载
对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型
加号运算符重载
作用:实现两个自定义数据类型相加的运算
-
成员函数重载加号
#include<iostream> using namespace std; class person{ public: int m_a; int m_b; person operator+(person &p){ person temp; temp.m_a = this->m_a + p.m_a; temp.m_b = this->m_b + p.m_b; return temp; } }; void test1(){ person p1,p2; p1.m_a = 10; p1.m_b = 10; p2.m_a = 10; p2.m_b = 10; //成员函数的本质调用 //person p3 = p1.operator+(p2); person p3 = p1 + p2; cout << p3.m_a << " " << p3.m_b << endl; } int main(){ test1(); return 0; } -
全局函数重载加号
#include<iostream> using namespace std; class person{ public: int m_a; int m_b; }; person operator+(person &p1, person &p2){ person temp; temp.m_a = p1.m_a + p2.m_a; temp.m_b = p1.m_b + p2.m_b; return temp; } void test1(){ person p1,p2; p1.m_a = 10; p1.m_b = 10; p2.m_a = 10; p2.m_b = 10; //全局函数本质调用 //person p3 = operator+(p1, p2); person p3 = p1 + p2; cout << p3.m_a << " " << p3.m_b << endl; } int main(){ test1(); return 0; }运算符重载,也可以发生函数重载
左移运算符
作用:可以输出自定义数据类型
#include<iostream>
using namespace std;
class person{
public:
int m_a;
int m_b;
//cout 是一个输出流的对象 p.operator<<(cout)
//不会利用成员函数重载左移运算符,因为无法实现cout在左侧
// void operator<<(cout){
// cout << this->m_a << " " << this->m_b << endl;
// }
};
//只能利用全局函数重载左移运算符
//cout operator<< p
ostream & operator<<(ostream &cout, person &p){
cout << "m_a = " << p.m_a << " "<< "m_b = " << p.m_b << endl;
return cout;
}
void test1(){
person p;
p.m_a = 10;
p.m_b = 10;
cout << p << endl;
}
int main(){
test1();
return 0;
}
递增运算符
作用:通过重载递增运算符,实现自己的整型数据
#include<iostream>
using namespace std;
class MyInteger{
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger(){
m_Num = 0;
}
//重载前置++运算符
MyInteger& operator++(){
m_Num++;
return *this;
}
//重载后置++运算符 int 代表占位参数,用于区分前置和后置递增
MyInteger operator++(int){
MyInteger temp = *this;
m_Num++;
return temp;
}
private:
int m_Num;
};
ostream& operator<<(ostream& cout, MyInteger myint){
cout << myint.m_Num;
return cout;
}
void test1(){
MyInteger myint;
cout << myint++ << endl;
cout << myint << endl;
}
int main(){
test1();
return 0;
}
赋值运算符重载
C++编译器至少给一个类添加4个函数:
- 默认构造函数
- 默认析构函数
- 默认拷贝看书
- 赋值运算符operator=,对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝的问题
#include<iostream>
using namespace std;
class person{
public:
int *m_age;
person(int age){
m_age = new int(age);
}
~person(){
if(m_age != NULL){
delete m_age;
m_age = NULL;
}
}
person& operator=(person &p){
*m_age = *p.m_age;
return *this;
}
};
void test1(){
person p1(18);
person p2(20);
person p3 = p1 = p2;
cout << *p1.m_age << endl;
cout << *p2.m_age << endl;
cout << *p3.m_age << endl;
}
int main(){
test1();
return 0;
}
关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
#include<iostream>
#include<string>
using namespace std;
class person{
public:
string m_name;
int m_age;
person(string name, int age){
m_name = name;
m_age = age;
}
bool operator==(person &p){
if(m_name == p.m_name && m_age == p.m_age)
return true;
else
return false;
}
};
void test1(){
person p1("tom", 18);
person p2("tom", 19);
if(p1 == p2){
cout << "p1与p2相等" << endl;
}
else{
cout << "p1与p2不相等" << endl;
}
}
int main(){
test1();
return 0;
}
函数调用运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方法非常像函数的调用,因此被称为仿函数
- 仿函数没有固定写法,非常灵活
#include<iostream>
#include<string>
using namespace std;
//打印输出类
class Myprint{
public:
void operator()(string test){
cout << test << endl;
}
};
void test1(){
Myprint myprint;
myprint("HelloWorld");
}
int main(){
test1();
return 0;
}
继承
继承的基本语法
继承的好处:减少重复代码
语法:class 子类 : 继承方式 父类
- 子类也称为派生类
- 父类也称为基类
#include<iostream>
using namespace std;
//公共页面
class BasePage{
public:
void header(){
cout << "首页、公开课、登入……" << endl;
}
void footer(){
cout << "帮助中心、交流合作……" << endl;
}
void left(){
cout << "java、python……" << endl;
}
};
//java页面继承BasePage
class Java: public BasePage{
public:
void content(){
cout << "java学科视频" << endl;
}
};
void test1(){
cout << "java下载视频" << endl;
Java ja;
ja.content();
ja.footer();
ja.header();
ja.left();
}
int main(){
test1();
return 9;
}
继承方式
继承方式一共有3种:
- 公共继承
- 保护继承
- 私有继承
继承中的对象模型
- 父类中所有非静态成员属性都会被子类继承下去
- 父类中私有属性是被编译器给隐藏了,因此是访问不到,但是确实是被继承下去了
继承中构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
父先构,子先析
继承同名成员处理方式
当子类和父类出现同名的成员时:
- 访问子类成员,直接访问即可
- 访问父类成员,需要加作用域
如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
#include<iostream>
using namespace std;
class Base{
public:
int m_a;
Base(){
m_a = 100;
}
};
class Son : public Base{
public:
int m_a;
Son(){
m_a = 200;
}
};
void test1(){
Son son;
cout << son.m_a << endl;
//如果通过子类对象访问父类中同名成员,需要加作用域
cout << son.Base::m_a << endl;
}
int main(){
test1();
return 0;
}
继承同名静态成员处理方法
静态成员和非静态成员出现同名,处理方式一致。
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
多继承语法
C++允许一个类继承多个类
语法:class 子类 : 继承方式 父类 , 继承方式 父类
- 多继承可能会引发父类中有同名成员出现,需要加作用域区分
- C++实际开发中不建议使用多继承
菱形继承
概念:
两个派生类继承同一个基类,又有某个类同时继承两个派生类。这种继承被称为菱形继承或者钻石继承
问题:
继承数据可能会产生二义性
两份从派生类继承的基类数据有重复。
- 利用虚继承解决菱形继承的问题
#include<iostream>
using namespace std;
class Animal{
public:
int m_age;
};
//继承之前叫上关键字virtual变成虚继承
//Animal类称为虚基类
class Sheep : virtual public Animal{
};
class Tuo : virtual public Animal{
};
class SheepTuo : public Sheep, public Tuo{
};
void test1(){
SheepTuo st;
st.Sheep::m_age = 18;
st.Tuo::m_age = 28;
cout << st.Sheep::m_age << endl;
cout << st.Tuo::m_age << endl;
}
int main(){
test1();
return 0;
}
多态
多态分为两类:
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
-
静态多态的函数地址早绑定--编译阶段确定函数地址
-
动态多态的函数地址晚绑定--运行阶段确定函数地址
-
动态多态满足条件
-
有继承关系
-
子类重写父类的虚函数
当发生重写的时候,子类中的虚函数表内部会替换称子类的虚函数地址
class Animal{ public: virtual void speak(){ } }; class Cat : public Animal{ public: virtual void speak(){ } }; Animal & animal = cat; animal.speak();
-
-
动态多态使用
父类的指针或者引用执行子类对象
-
多态的优点
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的拓展以及维护
在真实开发中,提倡开闭原则:对拓展进行开放,对修改进行关闭
#include<iostream>
#include<string>
using namespace std;
//实现计算器抽象类
class AbstractCalculator{
public:
int n_num1;
int n_num2;
virtual int getResult(){
return 0;
}
};
//加法计算器类
class AddCalculator : public AbstractCalculator{
public:
int getResult(){
return n_num1 + n_num2;
}
};
void test1(){
//多态使用条件
//父类指针或者引用指向子类对象
AbstractCalculator *abc = new AddCalculator;
abc->n_num1 = 10;
abc->n_num2 = 20;
cout << abc->getResult() << endl;
//用完后记得销毁
delete abc;
}
int main(){
test1();
return 0;
}
纯虚函数和抽象类
在多态中,通常父类中的虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改成纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0;
当类中有了纯虚函数,这个类也称为抽象类
抽象类的特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
#include<iostream>
using namespace std;
class Base {
public:
//纯虚函数
virtual void func() = 0;
};
class Son : public Base{
public:
virtual void func(){
cout << "实现Son的函数" << endl;
}
};
void test1(){
//Base b; 错误,抽象类无法实例化对象
//new Base; 错误,抽象类无法实例化对象
Son son; //子类必须重写父类的纯虚函数,否则也无法实例化对象
son.func();
}
int main(){
test1();
return 0;
}
虚析构和纯虚析构
多态使用时,如果子类中有属性开辟到栈区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
class Animal{
//纯虚析构需要声明也需要实现
//有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
virtual ~Animal() = 0;
};
Animal::~Animal(){
}
文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件
文件类型分为两种:
-
文本文件
文件以文本的ASCLL码形式存储在计算机中
-
二进制文件
文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂他们
操作文件的三大操作:
- ofstream 写操作
- ifstream 读操作
- fstream 读写操作
文本文件
写操作
写文件步骤如下:
-
包含头文件
#include<iostream> -
创建流对象
ofstream ofs; -
打开文件
ofs.open("文件路径",打开方式); -
写数据
ofs << "写入的数据"; -
关闭文件
ofs.close();
文件打开方式:
| 打开方式 | 解释 |
|---|---|
| ios::in | 为读文件而打开文件 |
| ios::out | 为写文件而打开文件 |
| ios::ate | 初始位置:文件尾 |
| ios::app | 追加方式写文件 |
| ios::trunc | 如果文件存在先删除,再创建 |
| ios::binary | 二进制方式 |
文件打开方式可以配合使用,利用|操作符
#include<iostream>
#include<fstream>
using namespace std;
int main(){
ofstream ofs;
ofs.open("test.txt",ios::out);
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close();
return 0;
}
读文件
读文件与写文件的步骤非常相似,但是读取方式相对比较多
读文件步骤:
-
包含头文件
#include<fstream> -
创建流对象
ifstream ifs; -
打开文件并判断文件是否打开成功
ifs.open("文件路径",打开方式); -
读数据
四种读取方式
#include<iostream> #include<fstream> #include<string> using namespace std; int main(){ ifstream ifs; ifs.open("test.txt", ios::in); if(!ifs.is_open()){ cout << "文件打开失败" << endl; return 0; } //读数据 //第一种 char buf[1024] = {0}; //类似于cin >> buf while(ifs >> buf){ cout << buf << endl; } //第二种 char buf[1024] = {0}; //ifs.getline(放入的指针地址, 最多读多少个字节数) while(ifs.getline(buf,sizeof(buf))){ cout << buf << endl; } //第三种 string buf; while(getline(ifs, buf)){ cout << buf << endl; } //第四种 char c; //EOF end of file 文件尾 //get 一个一个字节读 不推荐 while((c = ifs.get()) != EOF){ cout << c; } ifs.close(); return 0; } -
关闭文件
ifs.close();
二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为ios::binary
写文件
二进制方式写文件主要利用流对象调用成员函数write
函数原型ostream& write(const char*buffer, int len)
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
#include<iostream>
#include<fstream>
using namespace std;
class person{
public:
char m_name[64];
int m_age;
};
void test1(){
ofstream ofs;
ofs.open("person.txt", ios::out | ios::binary);
person p = {"张三", 18};
ofs.write((const char*)&p, sizeof(person));
ofs.close();
}
int main(){
test1();
return 0;
}
读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buff, int len)
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数
#include<iostream>
#include<fstream>
using namespace std;
class person{
public:
char m_name[64];
int m_age;
};
void test1(){
ifstream ifs;
ifs.open("person.txt", ios::in | ios::binary);
if(!(ifs.is_open())){
cout << "文件打开失败" << endl;
return;
}
person p;
ifs.read((char *)&p, sizeof(person));
cout << p.m_age << " " << p.m_name << endl;
ifs.close();
}
int main(){
test1();
return 0;
}


浙公网安备 33010602011771号