1、引用
作用:给变量起别名
语法:数据类型 &别名 = 原名
int a = 10;
int &b = a;
注意:
- 引用必须初始化
- 引用的本质是一个指针常量,引用在初始化后,不可以改变
2、函数调用
- 函数的调用可以作为左值
- 函数的默认参数(如果我们自己传入数据,就用自己的数据,如果没有,就用默认值)
- 如果函数声明有默认参数,函数实现就不能有默认参数
3、函数的重载
作用:函数名可以相同,提高复用性
满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同或者个数不同或顺序不同
函数的返回值不可以作为函数重载的条件
当函数重载碰到默认参数,出现二义性,会报错
4、类和对象
C++面向对象的三大特性:封装、继承、多态
4.1 封装
语法:class 类名{ 访问权限: 属性 / 行为};
class student
{
public:
/*
类中的属性和行为,统称为 成员
属性 称为 成员属性 成员方法
行为 称为 成员函数 成员方法
*/
int M_id; //属性
void showStudent() //行为
{
cout<<"学号:"<<M_id<<endl;
}
};
int main(){
student s1;
s1.M_id=2;
s1.showStudent();
system("pause");
return 0;
}
访问权限:
public: 类内可以访问,类外可以访问
protected: 类内可以访问,类外不可以访问 (儿子可以访问父亲中的保护内容)
private:类内可以访问,类外不可以访问(儿子不可以访问父亲的私有内容)
4.2 struct和class的区别
唯一的区别是 默认的访问权限不同
区别:
- struct 默认权限为公共
- class 默认权限为私有
4.3 成员属性设置为私有
好处:
- 可以自己控制读写权限
- 对于写可以检测数据的有效性
//可读可写
void setName(string name)
{
m_Name = name;
}
string getName()
{
return m_Name;
}
//只读
int getAge()
{
m_Age = 0;
return m_Age;
}
//只写
void setLover(string lover)
{
m_Lover = love;
}
4.4 立方体类案例
#include<iostream>
using namespace std;
#include<string>
class Cube
{
public:
void setL(int l)
{
L=l;
}
int getL()
{
return L;
}
void setW(int w)
{
W=w;
}
int getW()
{
return W;
}
void setH(int h)
{
H=h;
}
int getH()
{
return H;
}
int calculateS()
{
return 2*L*W+2*H*W+2*L*H;
}
int calculateV()
{
return L*W*H;
}
private:
int L;
int W;
int H;
};
int main(){
Cube c1;
c1.setL(10);
c1.setH(10);
c1.setW(10);
cout<<"面积为"<<c1.calculateS()<<endl;
cout<<"体积为"<<c1.calculateV()<<endl;
system("pause");
return 0;
}
4.5 构造函数和析构函数
构造函数:
语法: 类名(){}
- 构造函数,没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次
析构函数:
语法:~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称与类名相同,在名称前面加上符号~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构函数,无需手动调用,而且之会调用一次
4.6 构造函数的分类及调用
分类:
- 按照参数分类 无参构造(默认构造)和 有参构造
- 按照类型分类 普通构造 拷贝构造
//拷贝构造函数
Person(const Person &P)
{
//将传入的人身上的所有属性,拷贝到我身上
age = p.age;
}
调用:
// 1.括号法
Person p3(p2); // 拷贝构造函数
/*
调用默认构造函数时,不要加()
编译器会认为时一个函数的声明,不会认为在创建对象
*/
//2.显示法
Person p3 = Person(p2);
Person(p2);//匿名对象 当前执行结束后,系统会立即回收掉匿名对象
/*
Person(p3)
不要利用拷贝构造函数 初始化匿名对象,编译器会认为Person(p3)===Person p3; 对象声明
*/
//3.隐式转换法
Person p4 = 10; //相当于写了 Person p4= Person(10); 有参构造
Person p5 = p4; //拷贝构造
4.7 初始化列表
/*
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)
4.8 类对象作为类成员
C++类中的成员可以时另一个类的对象,我们称该成员为对象成员
class A{}
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员
当其他类对象作为本类成员,构造的时候先构造类对象,再构造自身,析构的顺序与构造相反
4.9 静态成员
- 静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享一个函数
- 静态成员函数只能访问静态成员变量
static void func()
{
……
}
void test01()
{
//1.通过对象访问
Person P;
p.func();
//2.通过类名访问
Person::func();
}
类外访问不到私有静态成员函数
4.10 this指针
this指针指向被调用的成员函数所属的对象
this指针时隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
Person(int age)
{
this->age = age;
}
Person& PersonAdd(Person p)
{
this->age +=p.age;
//返回对象本身
return *this;
}
int age;
};
4.11 const 修饰成员函数
- 常函数:
- 成员函数后加const后我们称为这个函数为常函数
- 常函数内不可以修改成员属性
- 成员属性声明同时加关键字mutable后,在常函数中依然可以修改
- 常对象:
- 声明对象前加const称为该对象为常对象
- 常对象只能调用常函数
public:
//this指针的本质是指针常量 指针的指向是不可以修改的
//在成员函数后面加const,修饰的是this指针,让指针指向的值也不可以修改
void showPerson() const //常函数
{
this->m_A=100;
}
int m_A;
};
void test02()
{
const Person p; //在对象前加const,变为常对象
p.m_A = 100;
p.showPerson(); //常对象只能调用常函数
}
5、 友元
友元的目的是让一个函数或者类访问另一个类中的私有成员
友元的关键字为friend
友元的三种实现:
- 全局函数做友元
- 类做友元
- 成员函数做友元
friend void googGay(Building *building); //全局函数
friend class GoodGay; //友元类
//告诉编译器 GoodGay类下的visit成员函数作为本类的好朋友,可以访问私有成员
friend void GoodGay::visit();
6、 运算符重载
概念:对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型
6.1 加号运算符重载
概念:对已有的运算符进行重新定义,赋予其另一种公牛,以适应不同的数据类型
//1. 成员函数重载+号
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;
}
//2. 全局函数重载+号
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;
}
也可以直接写成 Person p3=p1+p2
6.2 左移运算符重载
//不会利用成员函数重载<<运算符,因为无法实现cout在左侧 本质p.operator<<(cout)
//只能用全局函数重载左移运算符
ostream & operator<<(ostream &cout,Person &p)
{
cout<<"m_A="<<p.m_A<<"m_b="<<p.m_B;
return cout;
}
6.3 递增运算符重载
//重载前置++运算符,返回引用是为了一直对一个数据进行递增操作
MyInteger& operator++()
{
//先进行++运算符
m_Num++;
//再将自身做返回
return *this;
}
//重载后置++运算符
//void operator++(int) int代表占位参数,可以区分前置和后置
MyInteger operator++(int)
{
MyIntger temp = *this;
m_Num++;
return temp;
}
6.4 赋值运算符重载
C++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行进行值拷贝
- 赋值运算符 operator= 对属性进行值拷贝
//重载关系运算符
//重载 == 号
bool operator==(Person &p)
{
if(this->m_Name ==p.m_Name && this->m_Age == p.m_Age)
{
return true;
}
return false;
}
7、继承
下级的成员除了拥有上一级的共性,还有自己的特性
优点:减少重复代码
语法:class 子类:继承方式 父类
子类 (也称为派生类)
父类(也称为基类)
class Java:public BasePage{}
父类中所有非静态成员属性都会被子类继承下去
父类中私有成员属性,是被编译器给隐藏了,因此访问不到,但是确实被继承下去了
7.1 继承中的对象模型
步骤:
- 利用开发人员命令提示工具查看对象模型
- 跳转盘符 D:
- 跳转文件路径 cd 具体路径下
- 查看命令
- c1 /d1 reportSingleClassLayout 类名 文件名
继承中构造和析构的顺序如下:
先构造父类,再构造子类,析构的顺序与构造的顺序相反
7.2 继承中的同名成员处理
cout<<"Son 下m_A="<<s.m_A<<endl;
//如果是通过子类对象 访问父类中同名成员,需要加作用域
cout<<"Base 下m_A="<<s.Base::m_A<<endl;
s.fun();
//调用父类中同名成员函数
s.Base::fun();
s.Base:func(100);
- 子类对象可以直接访问到子类中同名成员
- 如果想访问到父类中隐藏的同名成员函数,需要加作用域
- 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数
7.3继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
Son s;
//1.通过对象访问
cout<<"Son 下m_A="<<s.m_A<<endl;
cout<<"Base 下m_A="<<s.Base::m_A<<endl;
//2.通过类名访问
cout<<"Son 下m_A="<<Son::m_A<<endl;
cout<<"Base 下m_A="<<Son::Base::m_A<<endl;
7.4 菱形继承
菱形继承概念:
- 两个派生类继承同一个基类
- 又有某个类同时继承着两个派生类
- 又称钻石继承
菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
利用虚继承可以解决菱形继承问题
8、多态
8.1 多态的基本概念
多态分为两类
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态的区别:
- 静态多态的函数地址早绑定 ——编译阶段确定函数地址
- 动态多态的函数地址晚绑定 ——运行阶段确定函数地址
#include<iostream>
using namespace std;
#include<string>
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;
}
};
/*动态多态满足条件
1.有继承关系
2.子类重写父类的虚函数
*/
//动态多态的使用
//父类的指针或者引用 执向子类对象
void doSpeak(Animal &animal)
{
animal.speak();
}
void test01()
{
Cat cat;
doSpeak(cat);
}
int main(){
test01();
system("pause");
return 0;
}
8.2 纯虚函数和抽象类
在多态中,通常父类中纯虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将 虚函数改为 纯虚函数
语法: virtual 返回值类型 函数名 {参数列表}=0;
当类中有了纯虚函数,这个类也称为 抽象类
抽象类的特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
8.3 虚析构和纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
虚析构语法:virtual ~类名(){}
纯虚析构语法:virtual ~类名() = 0;
9、文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件====
文件类型分为两种:
- 文本文件:文本文件以ASCII码形式存储在计算机中
- 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
- ofstream:写操作
- ifstream:读操作
- fstream:读写操作
9.1文本文件
写文件
步骤:
1.包含头文件
#include
2.创建流对象
ofstream ofs;
3.打开文件
ofs.open(“文件路径”,打开方式);
4.写数据
ofs<<“写入的数据”;
5.关闭文件
ofs.close();
读文件
步骤:
1.包含头文件
#include
2.创建流对象
ifstream ifs;
3.打开文件并判断文件是否打开成功
ifs.open(“文件路径”,打开方式);
4.读数据
四种方式读取
5.关闭文件
ifs.close();
9.2 二进制文件
写文件:
打开方式要指定为 ios::binary
二进制方式写文件主要利用流对象调用成员函数write
函数原型:ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len时读写的字节数
读文件
二进制方式读文件主要利用流对象调用成员函数read
函数原型 :istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间,len是读写的字节数