C++基础学习
2023-04-03 19:21:23

C++基础学习

基础语法(一些)

常量

记录程序种不可更改的数据

1、#define宏常量:文件上方定义,表示一个常量

2、const修饰的变量:变量定义前关键字,表示常量,不可修改

const int clo = 11;

数据类型

数据类型 占用空间
整型
short(短整型) 2字节
int(整型) 4字节
long(长整型) windows为4字节,Linux为4字节(32位),8字节(64位)
long long(长长整型) 8字节
浮点型
float(单精度) 4字节(7位有效数字)
double(双精度) 8字节(15~16位有效数字)
字符型
char 1字节(将对应ASCII码存入存储单元)
字符串型 规范
C语言风格 char 变量名[] = “字符串值”;
C++风格 string 变量名[] = “字符串值”;
布尔类型
true(1)/false(0) 1字节
float pai = 3.14f;//注意结尾f,未添加将识别为double类型

默认情况下输出一个小数只会显示6位有效数字

科学计数法:

float f1 = 3e2;//3*10^2
float f2 = 3e-2;//3*0.1^2

sizeof:统计数据类型所占内存大小,单位:字节

sizeof(数据类型/变量)

数据输出:cout

cout << 数据

数据输入:cin

cin >> 变量

逻辑运算符

!   //非
&&  //与
||  //或

选择结构与循环结构

三目运算符

返回值为变量,可以继续赋值

//语法
表达式1 ? 表达式2 : 表达式3

表达式1为真==》执行表达式2并返回表达式2,否则执行表达式3并返回表达式3

例:a,b做比较,将最大的值赋值给c

int a = 1;
int b = 2;
int c;
c = (a > b ? a : b);

switch注意点:switch表达式类型只能为整型或字符型

随机数

例:1~100

#include<iostream>
using namespace std;
#include<ctime>

int main()
{
	srand((unsigned int)time(NULL));
	int num = rand() % 100 + 1;
	
	cout << "随机数: " << num << endl;

	system("pause");
	return 0;
}

break:用于跳出选择结构或者循环结构

使用场景:

1、switch中,终止case并跳出switch

2、循环语句中,跳出当前的循环语句

3、嵌套循环中,跳出最近的内层循环语句

continue:循环语句中,跳过本次循环中剩下未执行的语句,继续进行下一次循环

goto:无条件跳转语句

语法:

goto 标记;
//若标记名称存在,当执行到goto语句时,则会跳转至标记的位置

数组

数组:存在一段连续的内存,使用相同的数据类型,定义数组必须有初始长度

*一维数组名可以直接打印数组的首地址

int arr[] = {1,2,3,4,5};
cout << arr << endl;  //将输出16进制的内存地址
cout << &arr[0] << endl;   //将输出数组第一个元素的内存地址	&:取地址

二维数组

数据类型 数组名[行数][列数];
数据类型 数组名[行数][列数] = {{数据1, 数据2} , {数据3, 数据4}};  可读性更强
数据类型 数组名[行数][列数] = {数据1,数据2,数据3,数据4};
数据类型 数组名[][列数] = {数据1,数据2,数据3,数据4};

函数

1、返回值类型,若不需要返回值==》void

2、函数名

3、参数表列

4、函数体语句

5、return表达式

语法:

返回值类型 函数名 (参数列表){
    函数体语句
    return 表达式
}

函数声明:提前告诉编译器该函数存在;

若函数定义位于调用函数之后,且函数未声明,将无法执行;

声明可以有多次,定义只能有一次;

若函数声明中存在默认参数,则在函数实现中不能有默认参数;

且在函数声明和函数实现中只能有一个有默认参数

占位参数:

语法:返回值类型 函数名(数据类型){}

占位参数也可以有默认参数,例 int = 10;

void test(int){
    cout << "hello world" << endl;
}

函数的分文件编写流程

1、创建.h的头文件

2、创建.cpp的源文件

3、在头文件中写函数声明

4、在源文件中写函数定义

函数重载

==》提高复用性

适用范围:

1、在同一个作用域下

2、函数名称相同

3、函数参数类型不同或者个数不同或者顺序不同

且:函数的返回值不能作为函数重载的条件

注意事项

//1、引用作为重载条件
void test(int &a){
    cout << "1" << endl;
}
void test(const int &a){  //类型不同
    cout << "2" << endl;
}
int main(){
    int a=10;
    test(a); //将调用第一个未重载前的函数
    //int &a = a合法
    
    test(10);//将调用重载后的函数
    //因为int &a = 10不合法
    //const int &a = 10合法
}

//2、函数重载与默认参数
void test(int a,int b = 10){
    cout << "1" << endl;
}
void test(int a){
    cout << "1" << endl;
}
int main(){
    test(10); //出现二义性,报错
}

指针

//定义指针
int a = 10;
int * p = &a;  //32位操作系统4字节,64操作系统8字节
cout << &a << endl; //a的地址
cout << p << endl;  //a的地址
cout << &p << endl; //p的地址,p中存放a的地址

//使用指针
*p = 1000;  //解引用
cout << a << endl;   //a被修改为1000
cout << *p << endl;  //1000
cout << p << endl;   //p中存放着a的地址

空指针无法被访问(0~255的内存编号是系统占用的,所以不可访问)

野指针:指针变量指向非法的内存空间(非申请空间)

int * p = (int *)0x1100;//地址空间并未申请
cout << *p << endl;

const修饰指针:

1、const修饰指针:常量指针,指针的指向可以修改,指针指向的值不能修改

int a = 10;
const int * p = &a;

2、const修饰常量:指针常量,指针的指向不可修改,指针指向的值可以修改

int a = 10;
int * const p = &a;

3、const既修饰指针,又修饰常量,指针的指向和指向的值都不能修改

int a = 10;
const int * const p = &a;

指针与数组

int arr[] = {1,2,3,4,5};
cout << arr[0] << endl;

int * p = arr; //数组首地址
cout << *p << endl;

p++;  //让指针向后偏移4个字节到达下一个元素地址
cout << *p << endl;

指针与函数

利用指针作为函数参数,可以修改实参的值

void swaqnum(int* a, int* b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

int main()
{
	int a = 10;
	int b = 20;

	swaqnum(&a, &b);  //地址传递
	cout << "a: " << a << endl;
	cout << "b: " << b << endl;

	system("pause");
	return 0;
}

冒泡排序

#include<iostream>
using namespace std;
//冒泡排序
void bubblezsSort(int* arr, int arrlen) {
	for (int i = 0; i < arrlen; i++)
	{
		for (int j = 0; j < arrlen-i-1; j++)
		{
			if (arr[j] > arr[j + 1]) {
				int temp = arr[j];  //临时
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}
//打印数组
void printarray(int* arr, int arraylen) {
	for (int i = 0; i < arraylen; i++)
	{
		cout << arr[i] << endl;
	}
}

int main()
{
	int arr[] = { 3,5,6,7,2,44,13 };

	//数组长度
	int arrlen = sizeof(arr) / sizeof(arr[0]);

	bubblezsSort(arr, arrlen);//排序 arr为数组首地址
	printarray(arr, arrlen);//输出
	
	system("pause");
	return 0;
}

结构体

用户自定义的数据类型,允许用户存储不同的数据类型

//创建结构体
strcut 结构体名{
    //结构体成员列表
    //例:
    string name;
    int age;
};

//创建结构体变量
//1、
struct 结构体名 变量名;  //struct关键字可以省略
变量名.name = "张三";  //访问结构体变量中的属性
变量名.age = 18;
//2、
struct 结构体名 变量名 = {"张三" , 18};  //struct关键字可以省略
//3、在创建结构体时顺便创建结构体变量
strcut 结构体名{
    //结构体成员列表
    //例:
    string name;
    int age;
}变量名;
//后续可以直接访问结构体变量

//结构体数组
struct 结构体名 数组名[] = {
    {"张三",18},
    {"李四",20}
};
数组名[下标].属性 = 值;//修改赋值

结构体指针:利用指针访问结构体中的成员

利用操作符 -> 可以通过结构体指针访问结构体属性

例:

struct sssss
{
	string name;
	int age;
};

sssss s = {"张三", 18}; //创建结构体变量
sssss* p = &s;  //创建结构体指针
cout << p->name << endl;  //通过结构体指针访问结构体成员变量
cout << p->age << endl;

结构体嵌套

struct 子结构体名{
    //成员变量
};
struct 结构体名{
    //其他成员变量
    
    //嵌套子结构体
    struct 子结构体名 子结构体变量;
}
//访问
父结构体变量.子结构体变量.子结构体属性

结构体作为函数参数

//1、值传递
void 函数名(struct 结构体名 结构体形参){
    cout << 结构体形参.成员变量 << endl;
};
//2、地址传递
//采用地址传递方式将可以减少内存空间的消耗
void 函数名(struct 结构体名 * 结构体指针){
    cout << 结构体指针->成员变量 << endl;
};
//const修饰
void 函数名(const struct 结构体名 * 结构体指针){
    结构体指针->成员变量 = 值;//不允许写操作,将报错
    cout << 结构体指针->成员变量 << endl;  //允许读操作
};

小例子

通讯录管理系统纯代码实现

#include<iostream>
using namespace std;

#define MAX 1000 //通讯录最大人数

//联系人结构体
struct Person {
	string name;
	int sex; //1:男 2:女
	int age;
	string phone;
	string addree;
};
//通讯录结构体
struct Addressbooks {
	Person personarray[MAX];  //联系人数组
	int P_Size;  //人员个数
};

//菜单
void showMenu() {
	cout << "1、添加联系人" << endl;
	cout << "2、显示联系人" << endl;
	cout << "3、删除联系人" << endl;
	cout << "4、查找联系人" << endl;
	cout << "5、修改联系人" << endl;
	cout << "6、清空联系人" << endl;
	cout << "0、退出程序" << endl;
	cout << "请输入你的选项: " << endl;
}

//添加联系人
void addPerson(Addressbooks * books) {
	//判断通讯录是否已满
	if (books->P_Size == MAX) {
		cout << "通讯录已满" << endl;
		return;
	}
	else
	{
		//添加联系人信息
		//姓名
		string name;
		cout << "请输入姓名: " << endl;
		cin >> name;
		books->personarray[books->P_Size].name = name;

		//性别
		int sex;
		while (true)
		{
			cout << "请输入姓别: " << endl;
			cout << "1 男" << endl;
			cout << "2 女" << endl;
			cin >> sex;
			if (sex == 1 || sex == 2)
			{
				books->personarray[books->P_Size].sex = sex;
				break;
			}
			cout << "输入的姓别选项有误 " << endl;
		}
		//年龄
		int age;
		cout << "请输入年龄: " << endl;
		cin >> age;
		books->personarray[books->P_Size].age = age;
		//电话
		string phone;
		cout << "请输入联系方式: " << endl;
		cin >> phone;
		books->personarray[books->P_Size].phone = phone;
		//住址
		string address;
		cout << "请输入住址: " << endl;
		cin >> address;
		books->personarray[books->P_Size].addree = address;

		//更新通讯录人数
		books->P_Size++;

		cout << "联系人添加成功" << endl;

		system("pause");//暂停进程,按任意键继续
		system("cls");//清屏
	}
}

//显示联系人
void printPerson(Addressbooks* books) {
	
	if (books->P_Size == 0) {
		cout << "当前的记录为空" << endl;
	}
	else
	{
		for (int i = 0; i < books->P_Size; i++)
		{
			cout << i + 1 << "、" << "姓名:" << books->personarray[i].name << "\t";
			//三目运算
			//cout << "性别:" << (books->personarray[i].sex == 1?"男":"女") << "\t";
			if (books->personarray[i].sex == 1)
			{
				cout << "姓别:男" << "\t";
			}
			else
			{
				cout << "姓别:女" << "\t";
			}			
			cout << "年龄:" << books->personarray[i].age << "\t";
			cout << "电话:" << books->personarray[i].phone << "\t";
			cout << "住址:" << books->personarray[i].addree << endl;
		}
	}
	system("pause");//暂停进程,按任意键继续
	system("cls");//清屏
}

//监测联系人是否存在
int isExist(Addressbooks* books, string name) {
	for (int i = 0; i < books->P_Size; i++)
	{
		if (books->personarray[i].name == name) {
			return i; //找到返回这个人在数组中的下标
		}
	}
	return -1; //未找到,返回-1
}

//删除联系人
void deletePerson(Addressbooks* books) {
	cout << "请输入将要删除的联系人的姓名:" << endl;
	string name;  //联系人姓名
	cin >> name;
	int ret = isExist(books, name);
	if (ret != -1)
	{
		//找到此人
		//数据前移,覆盖要删除的联系人
		for (int i = ret; i < books->P_Size; i++)
		{
			books->personarray[i] = books->personarray[i + 1];

		}
		books->P_Size--;//更新人员数
		cout << "删除成功" << endl;
	}
	else
	{
		cout << "查无此人" << endl;
	}
	system("pause");//暂停进程,按任意键继续
	system("cls");//清屏
}

//查找联系人
void findPerson(Addressbooks* books) {
	cout << "请输入要查找的联系人姓名:" << endl;
	string name;  //联系人姓名
	cin >> name;
	int ret = isExist(books, name);
	if (ret != -1)
	{
		//找到此人
		cout << ret + 1 << "、" << "姓名:" << books->personarray[ret].name << "\t";
		cout << "性别:" << (books->personarray[ret].sex == 1 ? "男" : "女") << "\t";
		cout << "年龄:" << books->personarray[ret].age << "\t";
		cout << "电话:" << books->personarray[ret].phone << "\t";
		cout << "住址:" << books->personarray[ret].addree << endl;
	}
	else
	{
		cout << "查无此人" << endl;
	}
	system("pause");//暂停进程,按任意键继续
	system("cls");//清屏
}
//修改联系人
void editPerson(Addressbooks* books) {
	cout << "请输入要修改的联系人姓名:" << endl;
	string name;  //联系人姓名
	cin >> name;
	int ret = isExist(books, name);
	if (ret != -1)
	{
		//找到此人
		//姓名
		cout << "开始修改:" << endl;
		cout << "请输入姓名: " << endl;
		cin >> name;
		books->personarray[ret].name = name;

		//性别
		int sex;
		while (true)
		{
			cout << "请输入姓别: " << endl;
			cout << "1 男" << endl;
			cout << "2 女" << endl;
			cin >> sex;
			if (sex == 1 || sex == 2)
			{
				books->personarray[ret].sex = sex;
				break;
			}
			cout << "输入的姓别选项有误 " << endl;
		}
		//年龄
		int age;
		cout << "请输入年龄: " << endl;
		cin >> age;
		books->personarray[ret].age = age;
		//电话
		string phone;
		cout << "请输入联系方式: " << endl;
		cin >> phone;
		books->personarray[ret].phone = phone;
		//住址
		string address;
		cout << "请输入住址: " << endl;
		cin >> address;
		books->personarray[ret].addree = address;
		cout << "联系人修改成功……" << endl;
	}
	else
	{
		cout << "查无此人" << endl;
	}
	system("pause");//暂停进程,按任意键继续
	system("cls");//清屏
}
//清空通讯录
void cleanPerson(Addressbooks* books) {
	int change;
	while (true)
	{
		cout << "请确认是否要清空通讯录" << endl;
		cout << "1 确认" << endl;
		cout << "0 取消" << endl;
		cin >> change;  //选项

		if (change == 1)
		{
			books->P_Size = 0;
			cout << "通讯录已清空……" << endl;
			break;
		}
		else if (change == 0) {
			//取消清空
			//退出清空功能
			cout << "退出清空联系人功能……" << endl;
			system("pause");//暂停进程,按任意键继续
			system("cls");//清屏
			return;
		}
		cout << "输入的选项不正确" << endl;
	}
	system("pause");//暂停进程,按任意键继续
	system("cls");//清屏
}

int main()
{	
	int select = 0;//用户选项变量
	Addressbooks books;  //通讯录结构体变量
	books.P_Size = 0; //初始化人员个数0
	
	while (true)
	{
		showMenu();  //菜单
		cin >> select; //输入选项

		switch (select)
		{
		case 1: //1、添加联系人
			addPerson(&books);  //地址传递
			break;
		case 2: //2、显示联系人(所有)
			printPerson(&books);
			break;
		case 3: //3、删除联系人
			deletePerson(&books);
			break;
		case 4: //4、查找联系人
			findPerson(&books);
			break;
		case 5: //5、修改联系人
			editPerson(&books);
			break;
		case 6: //6、清空联系人
			cleanPerson(&books);
			break;
		case 0: //0、退出程序
			cout << "欢迎下次使用" << endl;
			system("pause");
			return 0;
		default:
			cout << "输入的选项有误,请重新输入" << endl;
			break;
		}
	}
	system("pause");  //暂停进程
	return 0;
}

内存模型

内存分区模型==》不同的区域存放不同数据,给予不同的生命周期,实现灵活编程

1、代码区:存放函数体的二进制代码,由操作系统进行管理

2、全局区:存放全局变量和静态变量以及常量

3、栈区:由编译器自动分配释放,存放函数的参数值,局部变量等

注意事项:不能返回局部变量的地址

int * func(){
    int a = 10; //局部变量
    return &a;
}
int main() {
    int * p = func(); //接受返回值
    cout << *p << endl;  //第一次可以成功打印==》因为编译器做了保留
    cout << *p << endl;  //第二次数据将不再保留
    return 0;
}

4、堆区:由程序员分配和释放,若程序员不释放,在程序结束时由操作系统回收;在C++中通常使用new在堆区开辟内存,使用delete释放内存

//new返回的是该数据类型的指针
int * p = new int(10);  //开辟
int * arr = new int[10]; //创建数组
//释放
delete p;
delete[] arr; //释放数组

引用:给变量起别名,对引用的操作都会转为对引用对象的操作

数据类型 &别名 = 原名

引用必须初始化;引用一旦初始化后,将不允许更改

引用必须引一块合法的内存空间

int a = 10;
int &b = a;  //引用初始化
int c = 20;
b = c;   //此处进行的是赋值操作,不是更改引用
//执行后,a=20,b=20,c=20

引用作函数参数==》可以简化指针修改实参

//引用传递
void func(int &a,int &b){
    ……………………;
}
int main(){
    ……………………;
    int a =10;
    int b =20;
    func(a,b); //引用传递,形参会修饰实参
}

引用做返回值类型

//不能返回局部变量引用
//函数的调用可以作为左值
int& test(){
    static int a = 10; //静态变量,存放于全局区
    return a;
}
int main(){
    int &ret = test();  //引用
    test() = 100;  //对原名赋值
    cout << ret2 << endl; //别名将输出100
}

引用本质:在c++内部实现一个指针常量

int a = 10;

int& ret = a; //自动转换为int* const ret = &a; 指针常量=》指向不可改==》说明引用不可改
ret = 20;  //内部发现ret是引用==》自动转换为 *ret = 20;

常量引用:修饰形参

const int & ret = 10;
//经过const修饰后,编译器将代码修改为:
//且只读,不可修改
int temp = 10;  //合法临时空间
int& ret = temp;

void test(const int &val){
    val = 1000;//不合法操作,不允许修改
    //不使用const修饰将可以修改,并且实参也将被修改
}

类和对象

三大特性:封装,继承,多态

封装

将属性与行为作为一个整体,将属性与行为加以权限控制

语法:class 类名{ 访问权限: 属性 / 行为 };

#include<iostream>
using namespace std;

const double pie = 3.14;

class Circle
{
	//访问权限
	//公共权限
public:

	//属性
	//半径
	int m_r;

	//行为
	//获取圆的周长
	double ZC() {
		return 2 * pie * m_r;
	}
};

int main() {
	
	//通过圆类,创建圆对象
	Circle c1;
	//对圆对象赋值
	c1.m_r = 10;
	cout << "圆的周长:" << c1.ZC() << endl;

	system("pause");
	return 0;
}

权限:

1、pubic 公共权限,类内可以访问,类外也可以访问

2、protected 保护权限,类内可以访问,类外不可访问 儿子可以访问父亲中的保护内容

3、private 私有权限,类内可以访问,类外不可访问

struct与class的区别:

1、struct默认权限为公共

2、class默认权限为私有

成员属性设置私有:

1、将所有成员属性设置为私有,可以自己控制读写权限

2、对于写权限,可以检测数据的有效性

例:点与圆位置关系

#include<iostream>
using namespace std;

class Point
{
public:
	//获取x,y坐标
	int getX() {
		return m_x;
	}
	int gety() {
		return m_y;
	}
	//设置x,y坐标
	void setXY(int x,int y) {
		m_x = x;
		m_y = y;
	}

private:
	int m_x;
	int m_y;
};

class Circle
{
public:
	//设置半径
	void setR(int r) {
		m_r = r;
	}
	//获取半径
	int getR() {
		return m_r;
	}
	//设置圆心
	void setcenter(Point center) {
		m_center = center;
	}
	//获取圆心
	Point getcenter() {
		return m_center;
	}
private:
	int m_r;
	Point m_center;  //在类中可以让另外一个类作为本类中的成员
};

//判断点和圆的关系
void isInCircle(Circle &c, Point &p){
	//计算两点之间的距离平方
	int distance =
		pow(c.getcenter().getX() - p.getX(), 2) +
		pow(c.getcenter().gety() - p.gety(), 2);

	//计算半径的平方
	int rdistance = pow(c.getR(), 2);

	//判断关系
	if (distance == rdistance)
	{
		cout << "点在圆上" << endl;
	}
	else if(distance < rdistance) {
		cout << "点在圆内" << endl;
	}
	else
	{
		cout << "点在圆外" << endl;
	}
}

int main() {

	//创建圆
	Circle c;
	c.setR(3);
	//圆心
	Point center;
	center.setXY(0, 0);
	c.setcenter(center);

	//创建点
	Point p;
	p.setXY(0, 2);

	//判断关系
	isInCircle(c, p);

	system("pause");
	return 0;
}

对象的初始化和清理

构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用

析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作

构造函数语法:类名(){}

没有返回值也不写void;

函数名称与类名相同;

构造函数可以有参数,所以可以发生重载;

程序在调用对象时自动调用构造函数,无需手动调用,并且只会调用一次

析构函数语法:~类名(){}

没有返回值也不写void;

函数名称与类名相同,在名称前加上~;

析构函数不能有参数,因此不能发生重载;

程序在对象销毁时自动调用析构,无需手动调用,并且只能调用一次

构造函数的分类以及调用

分类:

1、按照参数分为:有参构造和无参构造

2、按照类型分为:普通构造和拷贝构造

调用:

1、括号法

2、显示法

3、隐式转换法

#include<iostream>
using namespace std;

class person
{
public:
	person() {
		cout << "无参构造函数" << endl;
	}
	person(int a) {
		cout << "有参构造函数" << endl;
	}
	person(const person& p) {
		cout << "拷贝构造函数" << endl;
	}
	~person() {
		cout << "析构函数" << endl;
	}
private:

};
void text() {
	//1、括号法
	person p1; //默认的构造函数调用;调用时不要使用();
	//若使用person p1();编译器将会将其认为是一个函数声明

	person p2(20); //有参构造函数
	person p3(p1); //拷贝构造函数

	//2、显示法
	person p4; //默认构造
	person p5 = person(10);  //有参构造
	person p6 = person(p5); //拷贝构造
	//匿名对象:
	person(10);
	cout << "执行结束即释放" << endl;
	//特点:当前执行结束后,系统会立即回收匿名对象

	//不能使用拷贝构造函数 初始化匿名对象
	//person(p3);
	//编译器会认为:person (p3) === person p3 ===>名称重定义

	//3、隐式转换法
	person p7 = 10;//相当于  person p7 = person(10);
	person p8 = p7;//拷贝构造
}

int main() {
	text();
	system("pause");
	return 0;
}

拷贝构造函数

使用场景:

使用一个已经创建完毕的对象来初始化一个新对象;

值传递的方式给函数参数传值;

以值方式返回局部对象。

//值传递
void test01(person p){
    
}
void test02(){
    person p;
    test01(p); //值传递
}

//以值方式返回局部对象
person test03(){
    person p1;
    return p1;
}
person test04(){
    person p = test03();
}

构造函数调用规则

1、如果用户定义有参构造函数,C++不在提供默认的无参构造,但是会提供默认拷贝构造

2、如果用户自定义了拷贝构造函数,C++将不在提供其他构造函数

深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作

深拷贝:在堆区申请空间,进行拷贝操作

注意:若属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题(重复释放同一块内存)

初始化列表:初始化属性

语法:构造函数():属性1(值1),属性2(值2) …… {}

#include<iostream>
using namespace std;

class person
{
public:
	person(int a,int b,int c):m_a(a),m_b(b),m_c(c){}
private:
	int m_a;
	int m_b;
	int m_c;
};
int main() {
	person p(1, 2, 3);
	system("pause");
	return 0;
}

类对象作为类成员

class A{}
class B{
    A a; //优先构造
}

构造的时候先构造类对象,再构造自身,析构的顺序与构造相反

静态成员

在成员变量和成员函数前+关键字static,称为静态成员

静态成员分为:

1、静态成员变量

所有对象共享同一份数据

在编译阶段分配内存

类内声明,类外初始化

2、静态成员函数

所有对象共享同一个函数

静态成员函数只能访问静态成员变量

class person
{
public:
	static int m_a;  //类内声明
private:
	
};
int person::m_a = 100; //类外初始化
int main() {
	person p1;
	person p2;
	p2.m_a = 200;
	cout << p1.m_a << endl; //200  共享内存
    cout << person::m_a << endl; //通过类名进行访问
	system("pause");
	return 0;
}

对象模型与this指针

类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象

空对象占用空间:1字节==》为了区分空对象占内存的位置

class person
{
public:
	
private:
	int m_a;  //非静态成员变量   属于类对象上

	static int m_b;  //静态成员变量 不属于类对象上

	void func1(){}  //非静态成员函数  不属于类对象上

	static void func2() {}  //静态成员函数  不属于类对象上
};

int main() {
	person p;
	cout << sizeof(p) << endl;  //4
	system("pause");
	return 0;
}

this指针:指向被调用的成员函数 所属的对象;

this指针是隐含每一个非静态成员函数内的一种指针;

this指针不需要定义,直接使用即可。

this指针使用范围:

1、当形参和成员变量同名时,可以使用this指针区分

2、在类的非静态成员函数中返回对象本身,可使用return *this;

#include<iostream>
using namespace std;

class person
{
public:
	person(int age) {
		this->age = age;
	}

	person& personaddage(person& p) {
		this->age += p.age;
		//this是指向p2的指针
		//*this指向p2对象本体
		return *this; 
	}
	int age;
private:
};

int main() {
	person p1(10);
	person p2(10);
	//链式编程
	p2.personaddage(p1).personaddage(p1);
	cout << p2.age << endl; //30

	system("pause");
	return 0;
}

空指针访问成员函数

#include<iostream>
using namespace std;

class person
{
public:

	void printperson() {
		cout << "this is person" << endl;
	}
	void showperson() {
		//若没有判断this指针将会报错
		//原因为传入的指针为NULL
		if (this == NULL) {
			return;
		}
		cout << "age = " << this->age << endl;
	}
private:
	int age;
};

int main() {
	person* p = NULL;
	p->printperson();
	p->showperson();
	system("pause");
	return 0;
}

const修饰成员函数

常函数:

1、成员函数后加上const称这个函数为常函数

2、常函数内不可以修改成员属性

3、成员属性声明时加上关键字mutable后,在常函数中依然可以修改

常对象:

1、声明对象前加上const称该对象为常对象

2、常对象只能调用常函数

class person
{
public:
	//this指针的本质:指针常量    指针的指向不能修改
	//person * const this
	//const person * const this  指向的值也不能改
	void func() const{
		this->m_a = 100;  //报错,不能修改
		this->m_b = 100;  //合法
		this = NULL;//报错,不能修改指针的指向
	}

	void showperson() {
		m_a = 100;
		cout << "show person" << endl;
	}

	int m_a;
	mutable int m_b; //特殊变量,即使在常函数中,也可以修改
private:
};
void test() {
	const person p;  //常对象
	p.m_a = 100;  //报错,不可修改
	p.m_b = 100;  //合法

	//常对象只能调用常函数
	p.func();  //合法
	p.showperson() //报错
}

友元

使一个函数或者类,访问另外一个类中私有成员

关键字:friend

实现:

1、全局函数做友元

class person
{
	//friend关键字
	//test将能访问person类中的私有成员
	friend void test(person* p);    //全局函数做友元
public:
	person() {
		m_a = 20;
		m_b = 30;
	}

	int m_a;
private:
	int m_b;  //私有

};
//全局函数
void test(person* p) {
	cout << p->m_a << endl;
	cout << p->m_b << endl;  //访问私有成员

}

void func() {
	person p;
	test(&p);
}

2、类做友元

class person
{
	friend class test;  //友元
public:
	person();
	int m_a;
private:
	int m_b;  //私有
};
//类外实现成员函数
person::person() {
	m_a = 20;
	m_b = 30;
}

class test
{
public:
	test();
	void func();  //用于访问person中的属性
	person* p;
private:
};
//类外实现成员函数
test::test()
{
	p = new person;  //在堆区创建对象
}
void test::func() {
	cout << p->m_a << endl;  //访问共有属性
	cout << p->m_b << endl;  //访问私有属性
}

void showperson() {
	test tt;
	tt.func();
}

3、成员函数做友元

class person;
class test
{
public:
	test();
	void func();  //用于访问person中的属性
	person* p;
private:
};
//类外实现成员函数
test::test()
{
	p = new person;  //在堆区创建对象
}
void test::func() {
	cout << p->m_a << endl;  //访问共有属性
	cout << p->m_b << endl;  //访问私有属性
}

class person
{
	friend void test::func();  //友元
public:
	person();
	int m_a;
private:
	int m_b;  //私有
};
//类外实现成员函数
person::person() {
	m_a = 20;
	m_b = 30;
}
void showperson() {
	test tt;
	tt.func();
}

运算符重载

运算符重载也能实现函数重载

注意:

1、对于内置的数据类型的表达式的运算符是不可改变的

2、不能滥用运算符重载

加号:实现两个自定义数据类型相加

class person
{
public:
	//成员函数重载+号
	//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;
private:
};

//全局函数重载+号
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 test() {
	person p1;
	p1.m_a = 10;
	p1.m_b = 20;
	person p2;
	p2.m_a = 30;
	p2.m_b = 40;

	person p3 = p1 + p2;
	cout << p3.m_a << "," << p3.m_b << endl;
}

左移运算符:可以输出自定义数据类型;不能使用成员函数重载,只能使用全局函数重载

左移运算符配合友元可以实现输出自定义数据类

//成员函数重载  无法实现cout在左侧
void operator<<(cout){}

//全局函数重载
//本质:operator<<(cout , p)  简化  cout << p
//若要访问类中的私有属性==》使用友元即可
//friend ostream & operator<<(ostream& cout,person& p);
ostream & operator<<(ostream& cout,person& p) {
	cout << p.m_a << p.m_b;
	return cout;
}

void test() {
	person p1;
	p1.m_a = 10;
	p1.m_b = 20;
	cout << p1 << endl;
}

递增运算符:实现自定义数据类型自增

class person
{
	friend ostream& operator<<(ostream& c, person p);
public:
	person();

	//前置递增
	person& operator++() {
		m_num++;
		return *this;
	}
	//后置递增
	//int :占位参数,用于区分前置和后置递增
	person& operator++(int) {
		person temp = *this;
		m_num++;
		return temp;  //先返回值,后递增
	}

private:
	int m_num;
};

person::person()
{
	m_num = 0;
}

//重载左移
ostream& operator<<(ostream& c, person p) {
	cout << p.m_num;
	return c;
}

void test() {
	cout << "前置递增" << endl;
	person p;
	cout << p << endl;
	cout << ++(++p) << endl;
	cout << p << endl;
}

void test01() {
	cout << "后置递增" << endl;
	person p;
	cout << p << endl;
	cout << p++ << endl;
}

赋值运算符重载:

C++编译器至少会给一个类添加4个函数

1、默认构造函数(无参,函数体为空)

2、默认析构函数(无参,函数体为空)

3、默认拷贝构造函数,对属性进行值拷贝

4、赋值运算符operator=,对属性进行值拷贝

若类中有属性指向堆区,做赋值操作的时候将会出现深浅拷贝的问题

class person
{
public:
	person(int m_b);
	~person();

	person& operator=(person &p) {
		//编译器提供了浅拷贝
		//m_a = p.m_a;
		//实现深拷贝
		//判断是否在堆区
		if (m_a != NULL)
		{
			delete m_a;  //先释放
			m_a = NULL;
		}
		//再深拷贝
		m_a = new int(*p.m_a);

		return *this;
	}

	int *m_a;
private:

};

person::person(int m_b)
{
	m_a = new int(m_b);
}

person::~person()
{
	if (m_a != NULL)
	{
		delete m_a;
		m_a = NULL;
	}
}

void test() {
	person p1(20);
	cout << *p1.m_a << endl;
	person p2(40);
	cout << *p2.m_a << endl;

	p2 = p1;
	cout << *p2.m_a << endl;

	person p3(70);
	cout << *p3.m_a << endl;
	
	p3 = p2 = p1;
	cout << *p3.m_a << endl;
}

关系运算符重载:相等,不相等,大于,小于

class person
{
public:
	person(string a,int b);
	~person();
	//相等重载
	bool operator==(person& p) {
		if (this->m_a == p.m_a && this->m_b == p.m_b) {
			return true;
		}
		return false;
	}
	//不相等重载
	bool operator!=(person& p) {
		if (this->m_a != p.m_a || this->m_b != p.m_b) {
			return true;
		}
		return false;
	}

	string m_a;
	int m_b;
private:

};

person::person(string a, int b)
{
	m_a = a;
	m_b = b;
}

person::~person()
{
}

void test() {
	person p1("closure", 20);
	person p2("closure", 20);
	if (p1 == p2)
	{
		cout << "p1 == p2" << endl;
	}
	person p3("closure", 20);
	person p4("closure", 21);
	if (p3 != p4)
	{
		cout << "p3 != p4" << endl;
	}
}

函数调用运算符重载:

函数调用运算符()也可以重载;由于重载后使用的方法非常像函数的调用==》称为仿函数

仿函数没有固定写法==》灵活

class person
{
public:
	void operator()(string text) {
		cout << text << endl;
	}
};

void test() {
	person p;
	p("closure");  //类似函数调用==》仿函数
}

继承

语法:

class 子类 : 继承方式 父类

子类也称为派生类;父类也称为基类

派生类中的成员分为两类,一类是从基类继承,一类是自己增加的成员;

从基类继承过来的表现其共性,新增的成员体现其个性;

继承方式:

1、公共继承

父类公共==》子类公共

父类保护==》子类保护

父类私有==》子类不可访问(编译器隐藏父类私有)

2、保护继承

父类公共==》子类保护

父类保护==》子类保护

父类私有==》子类不可访问(编译器隐藏父类私有)

3、私有继承

父类公共==》子类私有

父类保护==》子类私有

父类私有==》子类不可访问(编译器隐藏父类私有)

注意项:

继承时会将所有非静态成员属性都继承下去,包括私有属性;

继承后创建为子类创建对象将调用父类构造==》子类构造==》子类析构==》父类析构

查看对象模型:

使用VS开发开发人员命令提示符

进入对应项目所在路径,查看类的内存模型:

例:

class person
{
public:
	int m_a;
protected:
	int m_b;
private:
	int m_c;
};
//继承
class son : public person {
public:
	int m_d;
};

命令:

cl /d1 reportSingleClassLayout类名 "文件名"

当继承时父类和子类出现了同名成员

1、子类同名成员,直接访问即可

2、访问父类同名成员,需要加作用域

多继承:

一个类继承多个类

语法:class 子类 : 继承方式 父类1 , 继承方式 父类2……

多继承可能会引发父类中同名成员出现,需要加上作用域以区分

菱形继承:

两个派生类继承同一个基类,又有个类同时继承两个派生类。

class father{
public:
	int m_a;
};

class son1 : public father{};

class son2 : public father{};

class grandson : public son1 , public son2{};

void test() {
	grandson gg;

	//gg.m_a = 100; //报错,不明确
	gg.son1::m_a = 100;
	gg.son2::m_a = 200;
	//菱形继承/多继承,使用作用域区分
	//导致数据有两份==》资源浪费
	cout << gg.son1::m_a << endl;  //100
	cout << gg.son2::m_a << endl;  //200
    //cout << gg.m_a << endl;  报错,不唯一
}

===》解决问题

使用虚继承:在继承方式前加上virtual==》father类就将称为虚基类

class father{
public:
	int m_a;
};

class son1 :virtual public father{};

class son2 :virtual public father{};

class grandson : public son1 , public son2{};

void test() {
	grandson gg;

	//gg.m_a = 100; //报错,不明确
	gg.son1::m_a = 100;
	gg.son2::m_a = 200;
	//菱形继承/多继承,使用作用域区分
	//导致数据有两份==》资源浪费
	cout << gg.son1::m_a << endl;  //200
	cout << gg.son2::m_a << endl;  //200
    cout << gg.m_a << endl;  //200
    //使用虚继承后数据变得唯一
}

vbptr:虚基类指针,指向虚基类表(vbtable)

通过虚基类指针加上虚基类表中的偏移(8 / 4)即可指向唯一的m_a

多态

静态多态:函数重载和运算符重载属于静态多态(复用函数名)

动态多态:派生类和虚函数实现运行时多态

区别:

1、静态多态的函数地址早绑定==》编译阶段确定函数地址

2、动态多态的函数地址晚绑定==》运行阶段确定函数地址

class fath{
public:
	void func() {
		cout << "fath" << endl;
	}
};

class son : public fath {
public:
	void func() {
		cout << "son" << endl;
	}
};

//地址早绑定,编译阶段确定函数地址
void show(fath& fa) {  //fath & fa = s;
	fa.func();
}

void test() {
	son s;
	show(s);  //fath
}

//为了实现使用son下的func函数
//使用动态多态实现晚绑定===》将使用虚函数
class fath{
public:
	//虚函数
	virtual void func() {
		cout << "fath" << endl;
	}
};

class son : public fath {
public:
	void func() {
		cout << "son" << endl;
	}
};

//地址晚绑定
void show(fath& fa) {  //fath & fa = s;
	fa.func();
}

void test() {
	son s;
	show(s);  //son
}

动态多态总结:

1、需要满足有继承关系;

2、子类重写了父类的虚函数

在使用时要用父类的指针或者引用去执行子类对象

当父类中的func不是虚函数时

改为虚函数后:

当父类中的func为虚函数,且子类不对其进行重写时:

虚函数表中为&fath::func

当父类中的func为虚函数,且子类对其进行重写时:

虚函数表内原来的&fath::func将被覆盖为&son::func

纯虚函数:

语法:virtual 返回值类型 函数名 (参数列表) = 0;

当类中有了纯虚哈函数,这类将被称为抽象类;

抽象类无法实例化对象,且子类必须重写抽象类中的纯虚函数,否则也属于抽象类;

在多态中,通常父类中的虚函数的实现时无意义的==》因此可以将虚函数改为纯虚函数

class person {
public:
	virtual void func() = 0;  //纯虚函数
};

class son : public person {
public:
	//重写
	virtual void func() {
		cout << "func" << endl;
	}
};

void test() {
	person* per = new son;  //多态,父类指针指向子类对象
	per->func();
}

虚析构和纯虚析构:

在使用多态时,若子类中有属性开辟至堆区,那么父类指针在释放时无法调用到子类的析构函数;

===》将父类中的析构函数改为虚析构或者纯虚析构;

虚析构和纯虚析构共性:

可以解决父类指针释放子类对象;都需要具体的函数实现;

区别:

如果是纯虚析构,则该类属于抽象类,无法实例化对象;

语法:

虚析构:virtual ~类名(){}

纯虚析构:virtual ~类名() = 0;

class person {
public:
	person() {
		cout << "perosn构造" << endl;
	}
	//虚析构
	//virtual ~person() {
	//	cout << "perosn析构" << endl;
	//}
	//纯虚析构  此时person类为抽象类,无法实例化对象
	//需要声明也需要实现
	virtual ~person() = 0;
};
person::~person() {
	cout << "perosn纯虚析构" << endl;
}

class son : public person {
public:
	son() {
		cout << "son构造" << endl;
	}
	~son() {
		cout << "son析构" << endl;
		if (m_a != NULL) {
			delete m_a;
			m_a = NULL;
		}
	}

	void func(int a) {
		m_a = new int(a);  //堆区
	}
	int* m_a;
};

void test() {
	person* per = new son;  //多态,父类指针指向子类对象
	delete per;
}

文件操作

程序在运行时产生的数据都属于临时数据,程序一旦运行结束就都会被释放

通过文件操作可以将数据持久化,C++中对文件操作需要包含头文件<fstream>

文件类型:

1、文本文件:文件以文本的ASCII码形式存储在计算机中

2、二进制文件:文件以文本的二进制形式存储于计算机中

操作文件:

1、写:ofstream

2、读:ifstream

3、读写:fstream

文件打开方式:

打开方式 描述
ios::in 为了读文件而打开文件
ios::out 为了写文件而打开文件
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 若文件存在则先深处再创建
ios::binary 二进制方式

文件打开方式可以配合使用,利用|操作符

例:二进制方式写文件

ios::binary | ios::out

写文件:

//文本文件
#include<iostream>
using namespace std;
#include<fstream>
//写文件
void test() {
	ofstream offile;  //创建流对象
	offile.open("test.txt", ios::out);  //文件打开方式
	offile << "closure" << endl;
	offile << "zh-closure.github.io" << endl;  //写内容
	offile.close();  //关闭文件
}

//二进制
//主要利用流对象调用成员函数write
//函数原型:
ostream& write(const char * buffer,int len);
//字符指针buffer指向内存中的一段存储空间,len是读写的字节数
class person {
public:
	char m_name[64];
	int m_age;
};
void test() {
	ofstream offile;  //创建流对象
	offile.open("closure.txt", ios::out | ios::binary);
	//写文件
	person p = { "closure",20 };
	offile.write((const char*)&p, sizeof(person));
	offile.close();  //关闭文件
}

读文件:

//文本文件
#include<iostream>
using namespace std;
#include<string>
#include<fstream>
//写文件
void test() {
	ifstream offile;  //创建流对象
	offile.open("test.txt", ios::in);  //文件打开方式
	//判断文件是否打开成功
	if (!offile.is_open())
	{
		cout << "文件打开失败" << endl;
		return;
	}
	//读数据
	
    //1、
	//char buf[1024] = { 0 };
	/*while ( offile >> buf )
	{
		cout << buf << endl;
	}*/
    
    //2、
	//char buf[1024] = { 0 };
	/*while (offile.getline(buf,sizeof(buf)))
	{
		cout << buf << endl;
	}*/
    
    //3、
	/*string buf;
	while ( getline(offile , buf))
	{
		cout << buf << endl;
	}*/
    
    //4、一个一个读(不推荐使用)
	char c;
	while ( (c = offile.get()) != EOF )  //EOF end of file
	{
		cout << c;
	}

	offile.close();  //关闭文件
}

//二进制
//主要利用流对象调用成员函数read
//函数原型:
istream& read(char *buffer,int len);
//字符指针指向内存中的一段存储空间
class person {
public:
	char m_name[64];
	int m_age;
};

//写文件
void test() {
	ifstream iffile;  //创建流对象
	iffile.open("closure.txt", ios::in | ios::binary);
	//判断文件是否打开
	if (!iffile.is_open())
	{
		cout << "文件打开失败" << endl;
	}

	//写文件
	person p;
	iffile.read((char*)&p, sizeof(person));
	cout << p.m_name << p.m_age << endl;
	iffile.close();  //关闭文件
}