目录
1 内存分区模型
程序运行前
程序运行运行前,编译后:
01 代码区
代码区:存放函数的二进制代码,有操作系统进行管理
【共享,只读】
02 全局区
全局区:存放全局变量;静态变量(普通变量前面加static);常量(字符串常量和const修饰的全局变量(全局常量))
【在程序结束后由操作系统释放】
//不在全局区的数据:局部变量、const修饰的局部变量(局部常量)
程序运行后
03 栈区
栈区:由编译器自动分配释放,存放函数的形参,局部变量等
//不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
04 堆区
堆区:由程序员分配和释放,若程序员不释放,系统结束时由操作系统回收
//new可以创建堆区数据,返回的是创建的堆区的地址,要用指针接收
05 new操作符
int* arr = new int[10];//堆区开辟数组
delete[] arr;//释放数组的时候,要加[]
2 引用
06 引用的基本使用
作用:给变量起别名
语法:数据类型 &别名=原名;
07 引用注意事项
引用必须初始化
引用初始化后不可以改变
int a = 10;
//int& b;//err,引用必须初始化
int& b = a;
int c = 20;
b = c;//这是赋值操作,不是更改引用,把b的值赋值成c的值20
cout << a << b << c << endl;//20 20 20
08 引用做函数参数
引用传递让形参修饰实参,简化指针修改实参
通过引用参数产生的效果同按地址传递是一样的,引用的语法更清楚简单
//引用传递
void mySwap(int &a,int &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 1;
int b = 2;
cout << "a=" << a << endl;//1
cout << "b=" << b << endl;//2
mySwap(a, b);
cout << "swap a=" << a << endl;//2
cout << "swap b=" << b << endl;//1
system("pause");
return 0;
}
09 引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数返回值是引用时,函数调用可以作为左值
//返回局部变量引用,非法操作
int& test01()
{
int a = 10;//局部变量存放在栈区
return a;
}
//返回静态变量引用,可以执行
int& test02()
{
static int a = 10;//静态变量,存放在全局区,程序结束后系统释放
return a;
}
int main()
{
//1.不要返回局部变量的引用
int& ref = test01();
cout << ref << endl;//第一次正确,因为编译器做了保留
//cout << ref << endl;//第二次错误,因为a的内存已经释放
int& ref2 = test02();
cout << ref2 << endl;//10
cout << ref2 << endl;//10
cout << ref2 << endl;//10
//2.如果函数的返回值是引用,这个函数的调用可以作为左值进行赋值操作
test02() = 1000;
cout << ref2 << endl;//1000
cout << ref2 << endl;//1000
system("pause");
return 0;
}
10 引用的本质
引用的本质在C++内部实现是一个指针常量
(指针的指向是不可以修改,指针指向的值是可以改动)
11 常量引用
用来修饰形参,防止误操作
(在函数形参列表中,可以加const修饰形参,防止形参改变实参)
void showValue(const int& val)//函数中利用常量引用防止误操作修改实参
{
cout << val;
}
int main()
{
//常量引用:用来修饰形参,防止误操作
int a = 10;
//int& ref = 10;//err,引用必须引一块合法的内存空间
const int& ref = 10;//编译器帮修改为int temp=10;int &ref=temp;
//ref = 20;//加入const之后,变为只读,不可修改
int a = 100;
showValue(a);
system("pause");
return 0;
}
3 函数提高
12函数默认参数
语法:返回值类型 函数名(形参=默认值){ }
- C++中函数的形参列表中的形参是可以有默认值的
- 如果有传入数据,就用传入的数据,如果没有就用默认值
- 如果某个位置已经有了默认参数,那么这个位置之后,从左到右都必须有默认值
- 声明和实现只能有一个有默认参数;如果函数声明有默认参数,函数实现就不能有默认参数
13 函数占位参数
C++中函数形参列表里可以有占位参数,用来占位,调用函数时必须填补该位置
语法:返回值类型 函数名(数据类型){}
//占位参数还可以有默认参数 (int=10),有默认值时,调用时可以不用传值
14 函数重载
函数名可以相同,提高复用性
满足条件:
- 同一个作用域;
- 函数名称相同;
- 函数参数类型不同或者个数不同或者顺序不同
函数的返回值不可以作为函数重载的条件
注意事项:
- 引用可以作为函数重载条件,const int &a和int &a(const可以作为引用重载条件)
- 函数重载碰到默认参数,容易出现二义性,尽量避免
4 类和对象
面向对象三大特性:封装、继承、多态
万物是对象,有属性和行为
具有相同性质的对象,可以抽象为类
封装
16 封装的意义 属性和行为作为整体
//设计一个圆类,求周长
const double PI = 3.14;
class Circle
{
//访问权限:公共权限
public :
//属性
int m_r;//半径
//行为:获取圆的周长
double calculateZC()
{
return 2 * PI * m_r;
}
};
//实例化(通过一个类,创建一个对象的过程)
Circle c1;//通过圆类,创建具体的圆(对象)
c1.m_r = 10;//给圆的对象的属性赋值
cout << "圆的周长为:" << c1.calculateZC() << endl;
实例化:通过一个类,创建一个对象的过程
类中的属性和行为,统称为成员
- 属性 成员属性 成员变量
- 行为 成员函数 成员方法
类在设计时,可以把属性和行为放在不同的权限下,加以控制
18 三种访问权限
//公共权限 public 成员类内可以访问,类外可以访问
//保护权限 protected 类内可以访问,类外不可以访问 儿子可以访问父亲中的保护内容
//私有权限 private 类内可以访问,类外不可以访问 儿子不可以访问父亲中的私有内容
19 struct和class的区别:
默认的访问权限不同
struct默认权限是 公共 public
class默认权限是 私有 private
20 成员属性设置为私有的优点
- 可以自己控制读写权限
- 对于写权限,可以检测数据的有效性
//设计立方体类
//创建立方体类
class Cube {
public:
void setL(int l)
{
m_L = l;
}
void setW(int w)
{
m_W = w;
}
void setH(int h)
{
m_H = h;
}
int getL()
{
return m_L ;
}
int getW()
{
return m_W;
}
int getH()
{
return m_H;
}
int calculateS()
{
return 2 * m_L * m_W + 2 * m_H * m_W + 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;
else
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;
else
return false;
}
int main()
{
Cube c1;
c1.setL(10);
c1.setW(10);
c1.setH(10);
cout<<"面积为"<<c1.calculateS()<<endl;
cout << "体积为" << c1.calculateV() << endl;
Cube c2;
c2.setL(11);
c2.setW(10);
c2.setH(10);
cout << "面积为" << c2.calculateS() << endl;
cout << "体积为" << c2.calculateV() << endl;
//利用全局函数判断
bool ret=isSame(c1,c2);
if (ret)
cout << "相等" << endl;
else
cout << "不相等" <<endl;
//利用局部函数判断
bool ret1 = c1.isSamebyclass(c2);
if (ret1)
cout << "相等" << endl;
else
cout << "不相等" << endl;
system("pause");
return 0;
}
//判断点和圆的关系
在类中可以让另一个类 作为本类中的成员
可以把一个类拆在不同文件中 .h声明 .cpp实现
对象特性:对象的初始化和清理
每个对象也会有初识设置以及对象销毁前的清理数据的设置
23 构造函数和析构函数
编译器自动调用,完成对象初始化和清理工作
- 构造函数:创建对象时对象的成员属性赋值
- 析构函数:对象销毁前,清理工作
构造函数:类名(){}
- 没有返回值,不用写void
- 函数名与类名相同
- 构造函数可以有参数,可以发生重载
- 创建对象的时候,构造函数会自动调用,而且只调用一次
析构函数:~类名(){}
- 没有返回值,不用写void
- 函数名与类名相同
- 构造函数不可以有参数,不可以发生重载
- 创建对象的时候,构造函数会自动调用,而且只调用一次
24 构造函数的分类和调用
构造函数分类:
按照参数:有参构造和无参构造(默认构造)
按照类型:普通构造和拷贝构造
调用方式:
1.括号法2.显示法3.隐式转换法
class Person
{
public:
//无参构造
Person()
{
cout << "Person的无参(默认)构造函数调用" << endl;
}
//有参构造
Person(int a)
{
age = a;
cout << "Person的有参构造函数调用" << endl;
}
//拷贝构造函数
Person(const Person &p)//const+引用方式传入
{
//将传入的人身上的所有属性,拷贝到当前对象身上
age = p.age;
cout << "Person的拷贝构造函数调用" << endl;
}
~Person()
{
cout << "Person的析构函数调用" << endl;
}
int age;
};
void test01()
{
//1.括号法
Person p;//默认构造函数的调用,调用默认构造函数的时候,不要加()
Person p1(10);
Person p2(p1);//拷贝构造函数调用
cout << "p2的年龄为" << p1.age << endl;
cout << "p2的年龄为" << p2.age << endl;
//注意:调用默认构造函数的时候,不要加()
//Person p1();//err
//编译器会认为是一个函数的声明,不会认为在创建对象
2.显示法
Person p1;
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10);//匿名对象,特点:当前行执行结束后,系统会立即回收掉匿名对象
注意:不要利用拷贝构造函数 初始化匿名对象
Person(p3);编译器会认为在对象声明==Person p3;
3.隐式转换法
Person p4 = 10;//相当于 Person p4=Person(10);
Person p5 = p4;
}
int main()
{
test01();
system("pause");
return 0;
}
25 拷贝构造函数调用时机
拷贝构造函数调用时机三种情况:
- 使用一个已经创建完毕的对象来初始化一个对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
class Person
{
public:
Person()
{
cout << "默认构造函数的调用" << endl;
}
Person(int age)
{
m_Age = age;
cout << "有参构造函数的调用" << endl;
}
Person(const Person &p)
{
m_Age = p.m_Age;
cout << "拷贝构造函数的调用" << endl;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
int m_Age;
};
//1.使用一个已经创建完毕的对象来初始化一个对象
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年龄为:" << p2.m_Age << endl;
}
//2.值传递的方式给函数参数传值
void doWork(Person p)
{
}
void test02()
{
Person p;
doWork(p);
}
//3.以值方式返回局部对象
Person doWork2()
{
Person p1;
return p1;
}
void test03()
{
Person p = doWork2();
}
int main()
{
//test01();
//test02();
test03();
system("pause");
return 0;
}
26 构造函数调用规则
调用规则:
如果我们写了有参构造函数,编译器就不再提供默认构造,依然提供拷贝构造
如果我们写了拷贝构造函数,编译器就不再提供其他普通构造函数
27 深拷贝和浅拷贝
浅拷贝:简单的复制拷贝操作
浅拷贝带来的问题就是堆区的内存重复释放
深拷贝:在堆区重新申请空间,进行拷贝操作
//如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
class Person
{
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age,int height)
{
m_Age = age;
m_Height=new int(height);
cout << "Person的有参构造函数调用" << endl;
}
//自己实现拷贝构造函数,解决浅拷贝带来的问题
Person(const Person& p)
{
cout << "Person拷贝构造函数调用" << endl;
m_Age = p.m_Age;
//m_Height = p.m_Height;编译器默认实现的代码,浅拷贝
//深拷贝操作
m_Height = new int(*p.m_Height);
}
~Person()
{
//析构代码,将堆区开辟数据做释放操作
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
cout << "Person的析构函数调用" << endl;
}
int m_Age;//年龄
int* m_Height;
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为:" << p1.m_Age <<"身高为:"<<*p1.m_Height<< endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.m_Age << "身高为:" << *p2.m_Height << endl;
}
int main()
{
test01();
system("pause");
return 0;
}
28 初始化列表
//初始化列表
Person(int a,int b,int c) :m_A(a), m_B(b), m_C(c)
{
}
//属性赋初值
Person p(31,20,10);
29 类对象作为类成员
类中的成员可以是另一个类的对象,我们称该成员为对象成员
- 当其他类对象作为本类成员,构造时候先构造类对象,再构造自身
- 当其他类对象作为本类成员,析构时候先析构自身,再析构类对象
构造顺序与析构顺序相反
class Phone
{
public:
Phone(string pName)
{
m_PName = pName;
cout << "phone的构造函数调用" << endl;
}
~Phone()
{
cout << "phone的析构函数调用" << endl;
}
string m_PName;
};
class Person
{
public:
Person(string name, string pName):m_Name(name),m_phone(pName)
{
cout << "person的构造函数调用" << endl;
}
~Person()
{
cout << "person的析构函数调用" << endl;
}
string m_Name;
Phone m_phone;
};
30 静态成员
静态成员变量:
- 所有对象共享同一份数据
- 在编译阶段分配内存(程序运行前-全局区)
- 类内声明,类外初始化
class Person
{
public:
static int m_A;//类内声明
};
int Person::m_A = 100;//类外初始化
void test01()
{
Person p;
cout << p.m_A << endl;//100
Person p2;
p2.m_A = 200;//所有对象共享同一份数据
cout << p.m_A << endl;//200
}
静态成员变量,不属于某个对象上,所有对象都共享同一份数据
有两种访问方式:
- 通过对象进行访问
- 通过类名进行访问
Person p;
cout<<p.m_A<<endl;
cout<<Person::m_A<<endl;
静态成员变量有访问权限,类外访问不到私有静态成员变量
31 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
//两种访问方式:通过对象,通过类名
//静态成员函数也是有访问权限的,类外访问不到私有的静态成员函数
class Person
{
public:
//静态成员函数
static void func()
{
m_A = 100;//静态成员函数可以访问静态成员变量,共享的
//m_B = 200;//静态成员函数不可以访问非静态成员变量,因为无法区分到底是哪个对象的m_B
cout << "ststic void func()的调用" << endl;
}
static int m_A;//类内声明,类外初始化
int m_B;//非静态成员变量
};
int Person::m_A=0;//类外初始化
void test01()
{
//静态成员函数有两种访问方式
//通过对象访问
Person p;
p.func();
//通过类名访问,因为不属于某一个对象上,不创建对象也可以访问
Person::func();
}
对象特性:C++对象模型和this指针
32 成员变量和成员函数分开存储
只有非静态成员变量 属于对象上的数据
空对象占用内存空间为:1
//C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
//每个空对象也应该有一个独一无二的内存地址
class Person
{
int m_A;//非静态成员变量 属于对象上的数据
static int m_B;//静态成员变量 不属于类的对象上
void func(){}//非静态成员函数 不属于类的对象上
static void func2() {}//静态成员函数 不属于类的对象上
};
int Person::m_B=0;
void test02()
{
Person p;
cout << "sizeofp=" << sizeof(p) << endl;//4
}
33 this指针
this指针:指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用
用途:
//当形参和成员变量同名时,可用this指针来区分
//在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
Person(int age)
{
//this指针指向被调用的成员函数所属的对象
this->age = age;
}
//如果用Person返回就是用值的方式返回,会用到拷贝构造函数,Person返回的和自身是不一样的数据
//用引用方式返回,不会创建新的对象,会一直返回P2本体
Person& PersonAddAge(Person& p)//Person& 不返回引用就会通过拷贝构造创建新对象,就不是原来的对象了
{
this->age += p.age;
//this是指向p2的指针,*this指向的就是p2这个对象的本体
return *this;
}
int age;
};
void test02()
{
Person p1(10);
Person p2(10);
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);//链式编程思想
cout << "p2的年龄为:" << p2.age << endl;
}
void test01()
{
Person p1(18);
cout << "p1的年龄为:" << p1.age << endl;
}
test01();
test02();
34 空指针访问成员函数
C++中允许空指针调用成员函数,也要注意有没有用到this指针
//如果用到this指针,需要判断保证代码的健壮性
35 const修饰成员函数
常函数
成员函数后加const称为常函数
//常函数内不可以修改成员属性(指针指向的值不能修改)
//成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象
声明对象前加const称为常对象
//常对象只能调用常函数
友元
36 全局函数做友元
class Building
{
friend void goodgay(Building* builiding);//goodgay是Building好朋友,可以访问Builing中私有成员
public:
Building()
{
m_SittingRoom = "客厅";
m_BedRoom = "卧室";
}
public:
string m_SittingRoom;
private:
string m_BedRoom;
};
//全局函数
void goodgay(Building *builiding)
{
cout << "全局函数正在访问:" << builiding->m_BedRoom << endl;
cout << "全局函数正在访问:" << builiding->m_SittingRoom << endl;
}
37 友元类
friend class goodgay;//goodgay类是本类的好朋友,可以访问本类中私有的成员
38 成员函数做友元
friend void Goodgay::visit01();//goodgay类下的visit成员函数作为本类的好朋友,可以访问私有成员
C++运算符重载
运算符重载:对已有运算符重新进行定义,赋予另一种功能,以适应不同的数据类型
39 加号运算符重载
作用:实现两个自定义数据类型相加的运算
//对于内置的数据类型表达式运算符是不可能改变的
//不要滥用运算符重载
class Person
{
public:
//1.成员函数重载加号: Person p3=p1+p2;成员函数的本质是 Person p3=p1.operator+(p2);
/*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;
}*/
int m_A;
int m_B;
};
//2.全局函数重载加号: Person p3=p1+p2;全局函数的本质是 Person p3= operator+(p1,p2);
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;
}
//3.运算符重载也可以发生函数重载
Person operator+(Person& p1, int num)
{
Person temp;
temp.m_A = p1.m_A +num;
temp.m_B = p1.m_B +num;
return temp;
}
void test01()
{
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
//运算符重载也可以发生函数重载
Person p4 = p1 + 100;
cout << "p3.m_A=" << p3.m_A << endl;
cout << "p3.m_B=" << p3.m_B << endl;
cout << "p4.m_A=" << p4.m_A << endl;
cout << "p4.m_B=" << p4.m_B << endl;
}
40 左移运算符重载
输出左移运算符配合友元可以实现输出自定义数据类型
class Person
{
friend ostream& operator<<(ostream& cout, Person& p);
public:
Person(int a, int b)
{
m_A = a;
m_B = b;
}
private:
//不会利用成员函数重载左移运算符<<,因为无法实现cout在左侧
int m_A;
int m_B;
};
//只能利用全局函数重载左移运算符
ostream& operator<<(ostream &cout,Person &p)//本质是operator<<(cout,p) 简化cout<<p
{
cout << "m_A=" << p.m_A << endl;
cout << "m_B=" << p.m_B << endl;
return cout;//为了可以无限往后追加输出
}
void test01()
{
Person p(10, 10);
cout << p <<"hello"<< endl;;
}
41 递增运算符重载
通过重载递增运算符,实现自己的整形数据
前置递增返回参数,后置递增返回值
//++
//自定义整形
class MyInteger
{
friend ostream& operator<<(ostream& cout, MyInteger myint);
public:
MyInteger()
{
m_Num = 0;
}
//重载++运算符
//1.重载前置++
MyInteger& operator++()//返回引用是为了一直对一个数据进行递增操作
{
//先进行++运算
m_Num++;
//再将自身返回
return *this;
}
//2.重载后置++ //返回的是值,因为返回的是局部变量
MyInteger operator++(int)//int代表占位参数,可以用于区分前置和后置递增,编译器会认为这是后置递增重载
{
//先记录当时结果
MyInteger temp = *this;
//后递增
m_Num++;
//再返回之前记录的结果
return temp;
}
private:
int m_Num;
};
//重载左移运算符
ostream& operator<<(ostream& cout, MyInteger myint)
{
cout << myint.m_Num << endl;
return cout;
}
void test01()
{
MyInteger myint;
cout << ++(++myint) << endl;
}
void test02()
{
MyInteger myint;
cout << myint++ << endl;
cout << myint << endl;
}
test01();
test02();
42 赋值运算符重载
Person& operator=(Person& p)
{
//编译器提供的是浅拷贝
//m_Age=p.m_Age;
//应该先判断有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL)
{
delete m_Age;
m_Age = NULL;
}
m_Age=new int(*p.m_Age);//深拷贝
return *this;
}
43 关系运算符重载
//重载==
bool operator==(Person& p)
{
if (this->m_Age == p.m_Age && this->m_Name == p.m_Name)
{
return true;
}
else false;
}
//重载!=
bool operator!=(Person& p)
{
if (this->m_Age == p.m_Age && this->m_Name == p.m_Name)
{
return false;
}
else true;
}
44 函数调用运算符重载
函数调用运算符()也可以重载
由于重载后使用的方式非常像函数的调用,因此称为仿函数
仿函数没有固定的写法,非常灵活
//打印输出
class MyPrint
{
public:
//重载函数调用运算符
void operator()(string test)
{
cout << test << endl;
}
};
void test01()
{
MyPrint myPrint;
myPrint("hello");//由于使用起来类似于函数调用,称为仿函数
}
//仿函数非常灵活,没有固定的写法
//加法类
class MyAdd
{
public:
int operator()(int num1, int num2)
{
return num1 + num2;
}
};
void test02()
{
MyAdd myadd;
int ret = myadd(100, 200);
cout << ret << endl;
//匿名函数对象 //MyAdd()
cout << MyAdd()(100, 200) << endl;
}
int main()
{
test01();
test02();
return 0;
}
继承
45 基本语法
继承的好处:减少重复代码
语法: class 子类:继承方式 父类
class A : publib B
A类,子类也称为派生类
B类,父类也称为基类
派生类中的成员,包含两大部分:一类是从基类继承过来的,一类是自己增加的成员
从基类继承过来的表现其共性,而新增的成员体现其个性
46 继承方式
继承的三种方式:
- 公共继承
- 保护继承
- 私有继承
47 继承中的对象模型
class Base
{
public:
int a;
protected:
int b;
private:
int c;
};
class Son :public Base
{
public:
int d;
};
void test01()
{
//父类中所有静态成员属性都会被子类继承下去
//父类中私有成员属性是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了
cout << "size of Son=" << sizeof(Son) << endl;//16
}
利用开发人员命令提示工具(Developer Command Prompt for VS 2019)查看对象模型
//跳转盘符 F:
//跳转文件路径 cd 具体路径下
//查看命令 cl /d1 reportSingleClassLayout类名 文件名
F:\C++-code\0421继承>cl /d1 reportSingleClassLayoutSon "129对象基本模型.cpp"
48 构造和析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
继承中的构造和析构的顺序:
先构造父类,再构造子类;析构顺序与构造顺序相反
49 同名成员处理
当子类与父类出现同名的成员,如何通过子类对象,访问到其中的同名数据:
1.访问子类同名成员,直接访问即可
2.访问父类同名成员,需要加作用域
Son s;
cout << "Son的m_A=" << s.m_A << endl;
cout << "Base的m_A=" << s.Base::m_A << endl;
//如果通过子类对象,访问到父类中的同名成员,需要加作用域
s.func();//直接调用的是子类中的同名函数
s.Base::func();//调用的是父类的
- 如果子类中出现和父类中同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数(包括重载版本)
- 如果想访问到父类中被隐藏的同名成员函数,需要加作用域
50 同名静态成员处理
同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(通过对象和通过类名)
class Base
{
public:
static int m_A;
static void func()
{
cout << "Base 的func的调用" << endl;
}
};
int Base::m_A = 100;
class Son :public Base
{
public:
static int m_A;
static void func()
{
cout << "Son 的func的调用" << endl;
}
};
int Son::m_A = 200;
void test01()
{
//通过对象访问
Son s;
cout << "Son的m_A=" << s.m_A << endl;
cout << "Base的m_A=" << s.Base::m_A << endl;
//通过类名访问
cout << Son::m_A << endl;
cout << Base::m_A << endl;
cout << Son::Base::m_A << endl;
//第一个::代表通过类名方式访问,第二个::代表访问父类作用域下的m_A
}
void test02()
{
//通过对象访问
Son s;
s.func();
s.Base::func();
//通过类名访问
Son::func();
Son::Base::func();
}
51 多继承语法
C++允许一个类继承多个类
语法:class 子类:继承方式 父类1,继承方式 父类2
//多继承可能会引发父类中有同名成员出现,需要加作用域
//开发中不建议用多继承
//子类继承两个父类
class Son :public Base1, public Base2
//父类中出现同名成员时,需要加作用域区分
cout << s.Base1::m_A << endl;
cout << s.Base2::m_A << endl;
52 菱形继承问题以及解决方法
菱形继承、钻石继承:
- 两个派生类继承同一个类
- 又有某个类同时继承两个派生类
多态
53 多态的基本语法
动态多态满足条件:
- 要有继承关系
- 子类重写父类的虚函数 //重写:函数返回值类型 函数名 参数列表 完全相同
动态多态使用:父类的指针或引用 指向子类对象
54 多态的原理剖析
//示例:多态原理剖析
//动物类
class Animal
{
public:
virtual void speak()
{
cout << "动物在说话" << endl;
}
};
//猫类
class Cat :public Animal
{
public:
//虚函数
void speak()
{
cout << "小猫在说话" << endl;
}
};
//狗类
class Dog :public Animal
{
public:
//虚函数
void speak()
{
cout << "小狗在说话" << endl;
}
};
void doSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展及维护
在真实的开发中,提倡 开闭原则
开闭原则:对扩展进行开发,对修改进行关闭
56 纯虚函数和抽象函数
当类中有了纯虚函数,这个类就是抽象类
//抽象类无法实例化对象
//子类必须重写抽象类中的纯虚函数,否则也属于抽象类,无法实例化对象
58 虚析构和纯虚析构
父类指针在析构的时候,不会调用子类中析构函数,导致子类中如果有堆区属性,出现内存泄漏
虚析构和纯虚析构的共性:
- 可以解决父类指针释放子类对象时不干净的问题
- 都需要有具体的函数实现
虚析构和纯虚析构的区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构:virtual ~类名(){}
纯虚析构:virtual ~类名() = 0;//声明
类名 :: ~类名() {} //具体实现
总结:
- 虚析构和纯虚析构就是用来解决通过父类指针释放子类对象
- 如果子类中没有堆区数据,可以不写虚析构或纯虚析构
- 拥有纯虚析构函数的类也属于抽象类
5 文件操作
文本文件
143 写文件
写文件步骤:
- 包含头文件#include<fstream>
- 创建流对象 ofstream ofs;
- 打开文件 ofs.open("文件路径",打开方式);
- 写数据 ofs << "写入的数据";
- 关闭文件 ofs.close();
ofstream ofs;
ofs.open("text.txt", ios::out);
ofs << "姓名:张三" << endl;
ofs.close();
144 读文件
ifstream ifs;
ifs.open("text.txt", ios::in);
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
//读文件的四种方式
//1
char buf[1024] = { 0 };
while (ifs >> buf)
{
cout << buf << endl;
}
//2
char buf[1024] = { 0 };
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}
//3
string buf;
while (getline(ifs, buf))
{
cout << buf << endl;
}*
//4不推荐
char c;
while ((c = ifs.get()) != EOF)
{
cout << c;
}
二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为ios::binary
145 写文件
ofstream ofs;
ofs.open("Person.txt", ios::out | ios::binary);
Person p = { "张三",18 };
ofs.write((const char *)&p,sizeof(Person));
ofs.close();
146 读文件
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_name << p.m_age << endl;
ifs.close();