侧边栏壁纸
博主头像
慢行的骑兵博主等级

贪多嚼不烂,欲速则不达

  • 累计撰写 33 篇文章
  • 累计创建 27 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

C++基础

慢行的骑兵
2025-02-14 / 0 评论 / 0 点赞 / 17 阅读 / 10,891 字
  • 将编译器优化的部分做了讲解,理解了平时模糊的点;

一.基础概念

1.变量

  • 给指定的内存空间取名字,方便操作这段内存;

2.常量

  • 用于记录程序中不可以更改的数据;
  • 分类:宏常量和const修饰的常量;

3.关键字

  • 预先保留的单词或标识符;

4.标识符起名规则

  • 给标识符起名有一套规则;

5.数据类型

  • C++规定创建一个变量或常量,必须要指出相应的数据类型,否则无法给变量分配内存;
  • 意义:给变量分配合适的内存空间;

6.sizeOf

  • 统计数据类型所占用的大小

7.数据类型-实型

  • 用于表示小数;

8.字符型

  • 字符变量用于显示单个字符;
  • 字符型变量并不是把字符本身放到内存中存储,而是将对应的ASCII编码放入到存储单元;

9.转义字符

  • 用于不能显示出来的ASCII字符;

10.字符串

  • 两种风格:
    • C语言风格:char 变量名[] = “字符串值”
    • C++风格

11.布尔类型

  • 布尔数据类型代表真或假的值;
  • 只要是非0的都代表着真;

12.数据的输入

  • cin

13.算术运算符

  • 加减乘除
  • 加加与减减:前置,先对变量进行运算,后对表达式进行运算

14.赋值/比较/逻辑运算符

15.结构结构/循环结构/跳转语句

16.一维数组

  • 数组中的每一元素都是相同的数据类型
  • 数组是有连续的内存位置组成的

16.1.定义方式

16.2.数组名

  • 用途:1.可以统计整个数组在内存中的长度;2.可以获取数组在内存中的首地址,(int)数组名;

16.3.冒泡排序

  • 最常用的排序算法
    • 比较相邻的元素,如果第一个比第二个大,就交换它们;
    • 对每一对相邻元素做同样的工作,执行完毕,找到第一个最大值;
    • 重复以上步骤,每次比较次数-1,直到不需要比较;
  • 外层循环次数为:数组长度-1,内层循环次数为数组长度-1-当前轮数;

17.二维数组

17.1.定义方式

  • 方式1:数据类型 数组名 [行 ][列 ]
  • 方式2:数据类型 数组名 [行 ][列 ] = {{数据1,数据2},{数据3,数据4}}
  • 方式3:数据类型 数组名 [行 ][列 ] =
  • 方式4:数据类型 数组名 [ ][列 ] =

17.2.循环

  • 外层是行,内层是列

17.3.二维数组名称用途

  • 查看内存占用空间大小
  • 查看二维数组的首地址

18.函数

  • 将一段代码封装起来,减少重复代码
  • 定义/调用/值传递(形参变化不会影响实参),括号内的叫做形参
  • 函数的常见样式:无参无返/有参无返/无参有返/有参有返
  • 函数的声明
    • 作用:在函数定义之前告诉编译器,有这么一个函数存在
    • 好处:提高代码可读性/支持分离编译/提前检查错误/支持函数重载/避免隐式函数声明;
    • 声明可以多次,但调用只能一次

18.1.函数的分文件编写(实践)

  • 四个步骤
    • 1.定义头文件(.h结尾)
    • 2.定义源文件(.cpp结尾)
    • 3.在头文件中写函数声明
    • 4.在源文件中写函数定义

19.指针

  • 必须掌握的技术
  • 通过指针间接访问内存

19.1.定义指针

19.2.操作指针

  • 通过接引用的方式来找到指针指向的内存

19.3.指针所占内存空间

  • 32位的操作系统,占用4个字节;64位8个字节;

19.4.空指针

  • 指针指向内存中编号为0的空间
  • 用途:初始化指针变量
  • 注意:空指针指向的内存是可以访问的

19.5.野指针

  • 指针指向非法的内存空间
  • 空指针和野指针都不是我们申请的空间,因此不能访问;

19.6.const修饰指针

  • 在指针前面加上const,常量指针,特点:指针的指向可以更改,指针指向的值不可以修改;
  • 在指针后面加上const,指针常量:特点:指针的指向不可以更改,指向的值可以更改;
  • 指针前后都加const

19.7.指针和数组的配合使用

  • 目的:利用指针访问数组的元素
  • 代码敲一遍(DONE)

20.结构体

  • 属于用户自定义的类型(一些类型集合组成的一个类型),允许用户存储不同的数据类型

20.1.定义方式

  • 三种
#include<iostream>
#include<string>
using namespace std;

struct Student {
	string name;
	int age;
	int score;
} s3;

int main() {
	struct Student s1;//这里的struct关键字可以省略
	//给s1属性赋值,通过.访问结构体变量中的属性
	s1.name = "张三";
	s1.age = 18;
	s1.score = 100;
	cout << "姓名" << s1.name << "年龄" << s1.age << "分数" << s1.score << endl;

	//2.在创建结构体变量的同时给变量的属性进行赋值
	struct Student s2 = { "李四",19,80 };
	cout << "姓名" << s2.name << "年龄" << s2.age << "分数" << s2.score << endl;

	//3.在定义结构体的时候顺便定义一个结构体变量
	s3.name = "王五";
	s3.age = 21;
	s3.score = 100;
	cout << "姓名" << s3.name << "年龄" << s3.age << "分数" << s3.score << endl;

	system("pause");
	return 0;
}
  • 注意:1.定义结构体时的关键字是struct,不可以省略;2.创建结构体变量时,关键字struct可以省略;3.结构体变量利用操作符"."访问成员;

20.2.结构体数组

  • 语法:struct 结构体名 数组名[元素个数] = {{},{},…

20.3.结构体指针

  • 使用指针访问结构体中的成员
#include<iostream>
#include<string>
using namespace std;

struct Student {
	string name;
	int age;
	int score;
} s3;

int main() {
	//1.创建学生结构体变量
	struct Student s = { "张三",18,100 };
	//2.通过指针指向结构体变量
	struct Student* p = &s;
	//3.通过指针访问结构体变量中的数据
	cout << "姓名" << p->name << "年龄" << p->age << "分数" << p->score << endl;
	system("pause");
	return 0;
}

20.4.结构体嵌套结构体

  • 在结构体中可以定义另一个结构体作为成员,用来解决实际问题;

20.5.结构体作为函数参数

  • 值传递 和 地址传递(修改了形参,实参会跟着改变);

20.6.结构体const使用场景

  • 补充:将函数中的形参改为指针,可以减少内存空间,而且不会复制新的副本出来;
  • 加了const可以限制修改;
  • 作用:加const可以防止函数体中的误操作;

20.7.通讯管理系统案例

  • 已实践

21.内存模型

21.1.代码区

  • 存放函数体的二进制代码,(程序结束)由操作系统进行管理

  • 代码运行前的区域

21.2.全局区

  • 存放全局变量、静态变量和常量

  • 代码运行前的区域

  • 局部变量、局部常量不在全局区中

21.3.栈区

  • 由编译器自动分配释放,存放函数的参数值,局部变量等
  • 注意事项:不要返回局部变量的地址,结合案例(需要总结 & 记住)
#include<iostream>
using namespace std;

int* func() {
	int a = 10;//局部变量,存放在栈区,栈区的数据在函数执行完之后自动释放
	return &a;	//返回局部变量的地址
}

int main() {
	int* p = func();

	cout << *p << endl;//第一次可以打印正确的数字,是因为编译器做了保留
	cout << *p << endl;//第二次这个数据就不再保留了

	system("pause");

	return 0;
}

21.4.堆区

  • 由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
  • 主要:利用new在堆区开辟内存

21.5.内存四区的意义

  • 不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程

22.引用

  • 作用:给变量取别名

  • 注意事项:引用必须要初始化,引用初始化之后就不可以更改

  • 引用做函数参数

    • 作用:形参会修改实参
    • 优点:简化指针修改实参
  • 引用做函数的返回值

    • 不要返回局部变量的引用
    • 函数的调用可以作为左值(如果函数的返回值是引用,这个函数调用可以做为左值)
#include<iostream>
using namespace std;

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;
	cout << "ref = " << ref << endl;
	cout << "ref = " << ref << endl;
	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++推荐使用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了;

  • 常量引用

    • 修饰形参,防止误操作

23.函数-高级

23.1.默认参数

  • 声明和实现只能有一个默认参数

23.2.占位参数

  • 占位参数调用时必须补全;占位参数可以有默认值;

23.3.函数重载

  • 注意事项:
    • 1.引用可以做为重载条件
    • 2.函数重载碰到默认参数,会出现二义性(不调用之前可以运行成功,调用之后就会出现报错)
#include<iostream>
using namespace std;

void func(int& a) {
	cout << "func (int &a) 调用 " << endl;
}

void func(const int& a) {
	cout << "func (const int &a) 调用 " << endl;
}

void func2(int a, int b = 10) {
	cout << "func2(int a, int b = 10) 调用 " << endl;
}

void func2(int a) {
	cout << "func2(int a) 调用 " << endl;
}

int main() {
	int a = 10;
	func(a);//调用无const
	func(10);//调用有const

	//func2(10);//存在二义性
	func2(10, 20);//不存在二义性
	return 0;
}

24.类和对象

  • C++面向对象三大特征:封装、继承、多态
  • 万事万物都皆为对象,对象上有属性和行为

24.1.封装

  • 意义:将属性和行为作为一个整体,表现生活的事物;将属性和行为加以权限控制;
  • struct和class的区别:唯一的区别,默认的访问权限不同(前者默认public,后者默认private);

24.2.构造函数和析构函数

24.3.构造函数的分类

  • 按照参数分类:无参函数、有参函数
  • 按照类型分类:普通函数、拷贝函数
  • 构造函数的调用:实践
#include<iostream>
using namespace std;

class Person {
public:
	//无参(默认)构造函数
	Person() {
		cout << "无参构造函数!" << endl;
	}

	//有参构造函数
	Person(int a) {
		age = a;
		cout << "有参构造函数!" << endl;
	}

	//拷贝构造函数
	Person(const Person& p) {
		age = p.age;
		cout << "拷贝构造函数!" << endl;
	}

	//析构函数
	~Person() {
		cout << "析构函数函数!" << endl;
	}
private:
	int age;
};

//2、构造函数的调用
//调用无参构造函数
void test01() {
	Person p;//调用无参构造函数
}

//调用有参构造函数
void test02() {
	//2.1. 括号法,常用
	Person p1(10);

	//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明;
	//如:
	// Person p2();

	//2.2.显示法
	Person p2 = Person(10);
	Person p3 = Person(p2);
	//Person(10)单独写就是匿名对象,当前行结束之后,马上析构

	//2.3.隐式转换法
	Person p4 = 10; //Person p4 = Person(10);
	Person p5 = p4; //Person p5 = Person(p4);

	//注意2:不能利用 拷贝构造函数 初始化匿名对象,编译器认为是对象声明
	//Person p5(p4);

}

int main() {
	test01();
	test02();

	system("pause");
	return 0;
}

24.4.拷贝构造函数的调用时机

  • 1、使用一个已经创建完毕的对象来初始化一个新对象
  • 2、值传递的方式给函数参数传值
  • 3、值方式返回局部对象
#include<iostream>
using namespace std;

class Person {
public:
	//无参(默认)构造函数
	Person() {
		cout << "无参构造函数!" << endl;
	}

	//有参构造函数
	Person(int a) {
		age = a;
		cout << "有参构造函数!" << endl;
	}

	//拷贝构造函数
	Person(const Person& p) {
		age = p.age;
		cout << "拷贝构造函数!" << endl;
	}

	//析构函数
	~Person() {
		cout << "析构函数函数!" << endl;
	}
private:
	int age;
};

//1、使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
	Person p1(20);
	Person p2(p1);
}

void doWork(Person p) {

}

//2、值传递的方式给函数参数传值
void test02() {
	Person p;
	doWork(p);
}

Person doWork2() {
	Person p1;
	return p1;
}

//3、值方式返回局部对象
void test03() {
	Person p = doWork2();//doWork2的p1地址会跟p的地址不同;
}

int main() {
	//test01();
	//test02();
	test03();

	system("pause");
	return 0;
}

24.5.构造函数的调用规则

  • 如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造;
  • 如果用户定义拷贝构造函数,c++不会再提供其它构造函数;

24.6.浅拷贝与深拷贝

  • 浅拷贝:简单的复制拷贝操作;

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

  • 浅拷贝带来的问题:堆区内存的重复释放;

24.7.初始化列表

24.8.类对象作为类成员

  • 类成员的对象构造先执行
  • 析构函数的执行顺序是跟构造函数的执行顺序相反

24.9.类对象-静态成员

  • 类内声明,类外初始化必须的做,否则不能访问其内存
  • 所有对象都共享同一份数据
  • 编译阶段就分配内存
#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
	static int m_A;//类内声明
};

int Person::m_A = 100;//类外初始化

int main() {
	Person p;
	cout << p.m_A << endl;

	system("pause");
	return 0;
}

24.10.类对象-静态成员函数

  • 静态成员函数

  • 所有对象共享同一个函数

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

  • 注意事项:

    • 空对象占用内存空间为1
    • 非静态成员变量,属于类的对象上;
    • 静态成员变量,不属于类对象上;
    • 非静态成员函数,不属于类对象上;
    • 非静态成员函数,不属于类对象上;

24.11.类对象-this指针的用途

  • this指向被调用的成员函数所属的对象

  • 1.解决命名冲突

  • 2.返回对象本身用*this;

  • 代码(注意personAddAge返回值的不同时所存在的差异)

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

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

	//如果返回值为Person,即值的方式,那么就会返回新的对象
	//如果返回值为Person&,那么就会返回对象本身
	Person personAddAge(Person& p) {
		this->age += p.age;
		return *this;
	}

	int age;
};

void test01() {
	Person p1(18);
	cout << "p1的年龄为: " << p1.age << endl;
}

void test02() {
	Person p1(10);
	Person p2(10);

	p2.personAddAge(p1).personAddAge(p1).personAddAge(p1);

	cout << "p2的年龄为: " << p2.age << endl;
}

int main() {
	test02();

	system("pause");
	return 0;
}

24.12.类对象-空指针访问成员函数

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

class Person
{
public:
	void showClassName() {
		cout << "this is Person class " << endl;
	}

	void showPersonAge() {
		cout << "age = " << m_Age << endl;//m_Age 前面隐式包含 this
	}

	int m_Age;
};

void test01() {
	Person* p = NULL;
	p->showClassName();//不报错
	//p->showPersonAge();//存在报错
}

int main() {
	test01();

	system("pause");
	return 0;
}

24.13.类对象-const修饰成员函数

  • this的本质是指针常量,在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改;但是若增加了mutable修饰,依旧可以修改;
  • 常对象只能调用常函数
#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
	void showClassName() const{
		this->m_Age = 10;//不可以修改
	}

	int m_Age;
};

int main() {

	system("pause");
	return 0;
}

24.14.类对象-友元

  • 目的:让一个函数或类访问另一个类中私有成员

  • 全局函数做友元

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

class Building
{
	friend void goodGay(Building* building);

public:
	Building() {
		m_SittingRoom = "客厅";
		m_BedRoom = "卧室";
	}

public:
	string m_SittingRoom;

private:
	string m_BedRoom;
};

void goodGay(Building* building) {
	cout << "好基友全局函数 正在访问: " << building->m_SittingRoom << endl;
	cout << "好基友全局函数 正在访问: " << building->m_BedRoom << endl;
}

void test01() {
	Building building;
	goodGay(&building);
}

int main() {
	test01();
	system("pause");
	return 0;
}
  • 类做友元
#include<iostream>
#include<string>
using namespace std;

class Building;

class GoodGay {
public:
	GoodGay();
	void visit();
	Building* building;
};

class Building
{
	friend class GoodGay;//增加

public:
	Building();

public:
	string m_SittingRoom;

private:
	string m_BedRoom;
};

Building::Building() {
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}

GoodGay::GoodGay() {
	building = new Building;
}

void GoodGay::visit() {
	cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
	cout << "好基友类正在访问:" << building->m_BedRoom << endl;
}

void test01() {
	GoodGay gg;
	gg.visit();
}

int main() {
	test01();
	system("pause");
	return 0;
}
  • 成员函数做友元
#include<iostream>
#include<string>
using namespace std;

class Building;

class GoodGay {
public:
	GoodGay();
	void visit();
	void visit2();
	Building* building;
};

class Building
{
//告诉编译器,goodGay类中的visit成员函数是Building好朋友,可以访问私有内容
 friend void GoodGay::visit();

public:
	Building();

public:
	string m_SittingRoom;

private:
	string m_BedRoom;
};

Building::Building() {
	m_SittingRoom = "客厅";
	m_BedRoom = "卧室";
}

GoodGay::GoodGay() {
	building = new Building;
}

void GoodGay::visit() {
	cout << "好基友类正在访问:" << building->m_BedRoom << endl;
}

void GoodGay::visit2() {
	cout << "好基友类正在访问:" << building->m_SittingRoom << endl;
}

void test01() {
	GoodGay gg;
	gg.visit();
	gg.visit2();
}

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

24.15.运算符重载

  • 对简化的写法做了详细的解释,通俗易懂
  • 运算符重载也可以发生函数重载
  • 成员函数重载/全局函数重载

24.15.1.加号运算符

//成员函数重载本质调用
Person p3 = p1.operator+(p2);

//全局函数重载本质调用
Person p3 = operator+(p1,p2);
  • 注意:对于内置的数据类型的表达式的运算符不可能改变的;不要滥用运算符重载;

24.15.2.左移运算符

#include<iostream>
using namespace std;

class Person {
	friend ostream& operator<<(ostream& cout, Person& p);

public:
	Person(int a, int b) {
		m_A = a;
		m_B = b;
	}

private:
	int m_A;
	int m_B;
};

//本质 operator<<(cout,p),简化写法 cout << p
ostream& operator<<(ostream& cout, Person& p) {
	cout << "m_A = " << p.m_A << "m_B = " << p.m_B;
	return cout;
}

void test01() {
	Person p(10,10);
	/*p.m_A = 10;
	p.m_B = 10;*/

	cout << p << endl;
}

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

24.15.3.递增运算符

  • 如何区分递增在前和递增在后(暂时记忆吧)
//cout << ++myInt << endl;编译器会将其解析为:myInt.operator++();  // 调用前置自增运算符
//cout << myInt++ << endl;编译器会将其解析为:myInt.operator++(0);  // 调用后置自增运算符,参数 0 是占位符
//编译器根据语法规则来判断是前置还是后置自增:
//如果 ++ 出现在操作数之前(如 ++myInt),则调用 前置自增运算符 operator++()。
//如果 ++ 出现在操作数之后(如 myInt++),则调用 后置自增运算符 operator++(int)

#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;
	}

	//后置++
	MyInteger& operator++(int) {
		//先返回,再++
		MyInteger temp = *this;
		m_Num++;
		return temp;
	}

private:
	int m_Num;
};

ostream& operator<<(ostream& out, MyInteger myint) {
	out << myint.m_Num;
	return out;
}

void test01() {
	MyInteger myInt;
	cout << ++myInt << endl;
	cout << myInt << endl;
}

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

24.15.4.赋值运算符

#include<iostream>
using namespace std;

class Person {

public:
	Person(int age) {
		m_Age = new int(age);
	}

	//重载赋值运算符
	Person& operator=(Person &p) {
		if (m_Age != NULL) {
			delete m_Age;
			m_Age = NULL;
		}

		//提供深拷贝
		m_Age = new int(*p.m_Age);
		return *this;
	}

	~Person() {
		if (m_Age != NULL) {
			delete m_Age;
			m_Age = NULL;
		}
	}

	int* m_Age;
};

void test01() {
	Person p1(18);
	Person p2(19);
	Person p3(20);

	p1 = p2 = p3;

	cout << *p1.m_Age << endl;
	cout << *p2.m_Age << endl;
	cout << *p3.m_Age << endl;
}

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

24.15.5.关系运算运算符

24.15.6.函数调用运算符

#include<iostream>
using namespace std;

class MyAdd
{
public:
	int operator()(int v1, int v2) {
		return v1 + v2;
	}
};

void test01() {
	MyAdd add;
	int ret = add(10, 10);
	cout << "ret = " << ret << endl;
	cout << "MyAdd()(100,100) = " << MyAdd()(100, 100) << endl;
}

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

24.16.继承

  • 重点:非难点;

  • 父类的私有成员也被子类继承下去了,只是由编译器隐藏访问不到;

24.16.1.继承的构造和析构的顺序

  • 父构造、子构造、子析构、父析构;

24.16.2.同名成员的处理

  • 子类出现与父类同名的成员,编译器会隐藏父类所有的同名成员;
  • 通过子类对象访问父类对象的同名成员/函数,增加作用域;
  • 同名静态成员(通过对象/类型):增加作用域;

24.16.3.多继承

  • 当父类出现同名成员,存在二义性,增加作用域处理;
  • 实际开发中,不建议使用多继承;

24.16.4.菱形/钻石继承

  • 利用虚继承可以解决菱形继承的问题

  • 开发中不建议写改类方式的代码,但是若看到有这样的写法要能看明白

  • vbptr:虚基类指针,继承指针,指针通过偏移量找到变量;

24.17.多态

  • 静态多态:函数重载/运算符重载,编译阶段确定函数地址

  • 动态多态:派生类/虚函数实现,运行阶段确定函数地址

  • 动态多态满足条件

    • 1.有继承关系
    • 2.子类重写父类的虚函数
  • 动态多态的使用

    • 父类的指针或引用执行子类对象

24.17.1.多态的原理

  • 父类定义了虚函数,子类进行了重写
    • 父类的内部结构,会存在虚函数指针和虚函数表,表会记录虚函数的地址,子类继承父类时,会继承父类的虚函数指针和虚函数表,当子类重写父类的虚函数,子类的虚函数表,内部会替换成子类的虚函数地址

24.17.2.多态的好处

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的拓展及维护

24.17.3.纯虚函数

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

  • 当类中有了纯虚函数,这个类也称为抽象类

  • 抽象类特点

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

24.17.4.虚析构和纯虚析构

  • 两者特点:解决父类指针释放子类对象
  • 都需要有具体的函数实现
  • 纯虚析构:既需要声明,且需要实现;

24.18.文件操作

  • 通过文件将数据持久化
  • 分类:文本文件(ASCII码形式)/二进制文件
//二进制文件-写文件
#include<fstream>
#include<string>
using namespace std;

class Person {
public:
	char m_Name[64];
	int m_Age;
};

void test01() {
	//1、包含头文件

	//2、创建输出流对象
	ofstream ofs("person.txt",ios::out | ios::binary);
	//3、打开文件

	//4、写文件
	Person p = { "张三",18 };
	//&p 表示获取对象 p 的地址。p 是一个 Person 类型的对象,因此 &p 的类型是 Person*,即指向 Person 类型的指针。
	//来自deepseek,待验证
	//(const char*)&p 是一种 C-style cast,它将 Person* 类型的指针强制转换为 const char* 类型的指针。这种转换是合法的,因为 C++ 允许将任何指针类型转换为 char* 或 const char*。
	ofs.write((const char*)&p, sizeof(p));

	//5、关闭流
	ofs.close();
}

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

24.19.职工管理系统

  • done

25.模板

  • 泛型编程:通过模板来实现
  • 模板:通用的模具,大大提高复用性

25.1.函数模板

  • 建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,用一个虚拟的类型来代表
  • 语法
template[固定的关键字] <typename T>//typename可以替换成class
函数声明或定义
  • 注释事项
    • 必须推导出一直的数据类型T才能使用
    • 模板要确定T的类型才能使用
template<class T>
void func() {
	cout << "func调用" << endl;
}

void test02() {
	func<int>();//不增加<int>则不能调用func
}
  • 普通函数和函数模板

    • 普通函数在调用时可以发生自动类型转换(隐式类型转换);
    • 函数模板在调用时,如果用自动类型推导,不会发生隐式类型转换;
    • 如果利用显示指定类型的方式,可以发生隐式类型转换;
    • 建议使用显示执行类型的方式调用函数模板(因为可以自己确定通用类型T);
  • 普通函数和函数模板的调用规则

    • 普通函数和函数模板是可以发生重载的
    • 如果普通函数和函数模板都可以实现,优先调用普通函数(强制调用除外)
    • 可以通过空模板参数列表 强制 调用函数模板
    • 如果函数模板可以产生更好地匹配,优先调用函数模板
#include<iostream>
using namespace std;

template<class T>
void myPrint(T a,T b) {
	cout << "调用的函数模板" << endl;
}

void myPrint(int a, int b) {
	cout << "调用的普通函数" << endl;
}

void test01() {
	int a = 10;
	int b = 20;

	myPrint<>(a, b);	//空模板参数列表
}

int main() {
	test01();
	system("pause");
	return 0;
}
  • 模板的局限性
    • 模板不是万能的,有些特定数据类型,需要用具体化方式做特殊实现
#include<iostream>
using namespace std;
#include<string>

class Person {
public:
	Person(string name, int age) {
		this->m_Name = name;
		this->m_Age = age;
	}

	string m_Name;
	int m_Age;
};

template<class T>
bool myCompare(T &a, T &b) {
	if (a == b) {
		return true;
	}
	else {
		return false;
	}
}

//具体化Person的版本实现代码,具体化优先调用
template<> bool myCompare(Person& p1, Person& p2) {
	if (p1.m_Age == p2.m_Age && p1.m_Name == p2.m_Name) {
		return true;
	}
	else {
		return false;
	}
}

void test01() {
	Person p1("张三", 10);
	Person p2("李四", 10);

	bool ret = myCompare(p1, p2);
	if (ret) {
		cout << "p1 == p2" << endl;
	}
	else {
		cout << "p1 != p2" << endl;
	}
}

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

25.2.类模板

  • 创建一个通用类,类的成员数据类型可以不具体制定,用一个虚拟的类型来代表;
template[固定的关键字] <typename T>//typename可以替换成class
类定义
  • 类模板和函数模板的区别

    • 类模板没有自动类型推导的使用方式
    • 类模板在模板参数列表中可以有默认参数
  • 类模板中成员函数创建时机

    • 普通类中的成员函数一开始就可以创建
    • 类模板中的成员函数在调用时才创建
#include<iostream>
using namespace std;
#include<string>

class Person1 {
public:
	void showPerson1() {
		cout << "Person1 show" << endl;
	}
};

class Person2 {
public:
	void showPerson2() {
		cout << "Person2 show" << endl;
	}
};

template<class T>
class MyClass {
public:
	T obj;

	void fun1() {
		obj.showPerson1();
	}

	void fun2() {
		obj.showPerson2();
	}
};

void tet01() {
	MyClass<Person1> m;
	m.fun1();
	//m.fun2();//编译会出错,说明函数调用才回去创建成员函数
}

int main() {
	tet01();
	system("pause");
	return 0;
}
  • 类模板对象做函数参数
    • 指定参数类型(使用比较广泛的方式)
    • 参数模板化
    • 整个类模板化
#include<iostream>
using namespace std;

template<class T1,class T2>
class Person {
public:
	Person(T1 name, T2 age) {
		this->m_Name = name;
		this->m_Age = age;
	}

	void showPerson() {
		cout << "姓名:" << this->m_Name << " 年龄: " << this->m_Age << endl;
	}

	T1 m_Name;
	T2 m_Age;
};

//1.类模板的对象作为函数参数
void printPerson1(Person<string, int> &p) {
	p.showPerson();
}

void test01() {
	Person<string, int>p("孙悟空",100);
	printPerson1(p);
}

//2.参数模板化
template<class T1,class T2>
void printPerson2(Person<T1, T2>& p) {
	p.showPerson();
}

void test02() {
	Person<string, int>p("二师兄", 100);
	printPerson2(p);
}

//3.整个类模板化
template<class T>
void printPerson3(T& p) {
	p.showPerson();
}

void test03() {
	Person<string, int>p("唐僧", 100);
	printPerson3(p);
}

int main() {
	test03();
	system("pause");
	return 0;
}
  • 类模板与继承
    • 如果父类是类模板,子类需要指定出父类中T的数据类型
#include<iostream>
using namespace std;

template<class T>
class Base {
public:
	T m;
};

//必须要知道父类的T类型,才能继承给子类
class Son :public Base<int> {
	Son2<int, char> S2;
};

//如果想灵活指定父类中T类型,子类也需要变类模板
template<class T1,class T2>
class Son2 :public Base<T2> {
	T1 obj;
};

int main() {
	
	system("pause");
	return 0;
}
  • 类模板函数类外实现
    • 需要加上模板参数列表
#include<iostream>
using namespace std;
#include<string>

template<class T1,class T2>
class Person {
public:
	Person(T1 name, T2 age) {
		this->m_Name = name;
		this->m_Age = age;
	}

	void showPerson() {
		cout << "姓名:" << this->m_Name << "年龄: " << this->m_Age << endl;
	}

	T1 m_Name;;
	T2 m_Age;
};

//构造函数的类外实现
template<class T1,class T2>
Person<T1, T2>::Person(T1 name, T2 age) {

}

//成员函数类外实现
template<class T1, class T2>
void Person<T1,T2>::showPerson() {

}

int main() {
	system("pause");
	return 0;
}
  • 类模板分文件编写

    • 问题的产生:无法解析外部文件(因为类模板中的成员函数创建时机在调用阶段,导致分文件时链接不到)
    • 解决方案,其一(一般不会这么做):直接包含源码;其二(建议方案):将.h和.cpp写在一起,将后缀名改为.hpp文件(.hpp是约定的名称,不是强制);
  • 类模板与友元

    • 全局函数类内实现:直接在类内声明友元即可;
    • 全局函数类外实现:需要提前让编译器知道全局函数的存在;
#include<iostream>
using namespace std;
#include<string>

//让编译器知道Person的存在
template<class T1,class T2>
class Person;

//全局函数,不需要加作用域(Person::)	//	函数模板的实现
template<class T1, class T2>
void printPerson2(Person<T1, T2> p) {
	cout << "类外实现 --- 姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
}

template<class T1,class T2>
class Person {

	//1.全局函数-类内实现
	friend void printPerson(Person<T1,T2> p) {
		cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
	}

	//2.全局函数类外实现	& 需要让编译器提前知道改函数的存在	
	//friend void printPerson2(Person<T1, T2> p);//普通函数的声明
	friend void printPerson2<>(Person<T1, T2> p);//增加空模板参数列表

public:
	Person(T1 name, T2 age) {
		this->m_Name = name;
		this->m_Age = age;
	}

private:
	T1 m_Name;
	T2 m_Age;
};

void test01() {
	Person<string, int> p("Tom", 20);
	printPerson(p);
}

void test02() {
	Person<string, int> p("Tom2", 20);
	printPerson2(p);
}

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

25.3.STL

25.3.1.STL六大组件(面试可能会考察),大体划分

  • 容器、算法、迭代器、仿函数、适配器、空间配置器;
    STL大体分类

26.容器

26.1.vector-存放内置数据类型容器

  • vector:和数组很类似,也称为单端数组,同数组的区别,数组是静态空间,vector可以动态拓展;

26.2.string容器

  • C++的风格,本质上是一个类,内部维护了char* 数组;
  • 构造函数
#include<iostream>
using namespace std;
#include<string>
/*
* string()						创建一个空的字符串
* string(const string& str)		使用字符串s初始化
* string(const string& str)		使用一个string对象初始化另一个string对象
* string(int n,char c)			使用n个字符串c初始化
*/
int main() {
	string s1;

	const char* str = "hello world";
	string s2(str);

	cout << "s2 = " << s2 << endl;

	string s3(s2);

	cout << "s3 = " << s3 << endl;

	string s4(10, 'a');
	cout << "s4 = " << s4 << endl;

	system("pause");
	return 0;
}
  • 赋值操作
  • 字符串拼接
  • 字符串查找和替换
    字符串查找和替换
  • 字符串比较(按照字符串的ASCII进行比较)
  • 字符串存取
  • 字符串插入与删除
  • 字串获取

26.3.vector构造

  • 支持随机访问
  • 构造
    • 默认构造
    • 区间构造
    • n个element构造
    • 拷贝构造(常用)

26.4.vector赋值操作

  • =号/区间/n个element

26.5.vector容量和大小

  • empty/size/capacity/resize

26.6.vector插入和删除

vector插入和删除

26.7.vector数据存取

  • at/索引/front(第一个元素)/back(最后一个元素)

26.8.vector互换容器

  • swap
  • 巧用swap可以收缩内存空间

26.9.vector预留空间

  • reserve(int len)

26.10.deque构造函数

  • vector和deque的区别:
    • vector对于头部的插入效率低,数据量越大,效率越低;
    • deque相比vector而言,对头部的插入和删除比vector快;
    • vector访问元素时的速度会比deque快;

26.11.deque赋值、大小、插入、删除、存取、排序

26.12.stack容器

stack常用接口

26.13.queue容器

queu常用接口

26.14.list容器

  • list和vector是两个最常被使用的容器
  • list本质是链表,非连续性空间存储数据

26.14.1.list构造

  • 普通构造/区间/拷贝/n个element

26.14.2.list赋值和交换

list赋值和交换

26.14.3.list大小操作

list大小操作

26.14.4.list插入和删除

list插入和删除

26.14.5.list数据存取

  • front/back

26.14.5.list反转和排序

  • reverse/sort(可以传入比较器)

26.15.set容器

  • 关联式容器,底层是二叉树实现;

  • 插入和删除
    set容器的插入和删除

  • 拷贝构造

  • 大小和交换:size()/empty()/swap(set)

  • 查找和统计:find(key)-返回的是迭代器/count(key)

  • set和multiset区别:前者不可以插入重复数据,后者可以插入重复数据

26.16.map容器

  • 复制、大小和交换、插入和删除、查找和统计、排序

26.17.函数对象基本使用

  • 函数对象(仿函数)是一个类,不是一个函数

  • 特点:函数对象调用时,可以像普通函数那样,可以有参数,可以有返回值;函数对象可以作为参数传递;函数对象超出普通函数的概念,函数对象可有自己的状态;

  • 谓词:仿函数返回值类型为bool数据类型,称为谓词

  • 一元谓词:如果operator()接受了一个参数,那么叫做一元谓词

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

class GreaterFive {
public:
	bool operator()(int val) {
		return val > 5;
	}
};

void test01() {
	vector<int> v;
	for (int i = 0; i < 10; i++) {
		v.push_back(i);
	}

	//参数3:匿名函数对象
	vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterFive());

	if (it == v.end()) {
		cout << "未找到" << endl;
	}
	else {
		cout << "找到了大于5的数字为:"<< *it << endl;
	}
}

int main() {
	test01();
	system("pause");
	return 0;
}
  • 二元谓词:如果operator()接受了两个参数,那么叫做二元谓词

  • 内建函数对象:STL内建了一些函数对象

    • 算术仿函数:加/减/乘/除
    • 关系仿函数:等于/不等于/大于(最常用)/不等于/小于/小于等于
    • 逻辑仿函数

27.STL(常用)算法

27.1.遍历

  • for_each:遍历容器
    for_each遍历
  • transform:搬运容器到另一个容器中
    transform遍历

27.2.查找

查找算法

  • find:按值查找元素,找到返回指定元素的迭代器,找不到返回结束迭代器end;
  • find_if:按条件查找元素,找到返回指定元素的迭代器,找不到返回结束迭代器end;
  • adjacent_find:查找相邻重复元素,返回相邻元素的第一个位置的迭代器。面试题若出现相邻重复元素,记得使用STL中的adjacent_find;
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

void test01() {
	vector<int> v;
	v.push_back(0);
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(1);
	v.push_back(4);
	v.push_back(5);
	v.push_back(5);

	vector<int>::iterator pos = adjacent_find(v.begin(), v.end());
	if (pos != v.end()) {
		cout << "找到了" << endl;
	}
	else {
		cout << "未找到" << endl;
	}
}

int main() {
	test01();
	system("pause");
	return 0;
}
  • binary_search:查找指定元素是否存在,查找到了就返回true,没找到就返回false;注意:在无需序列中不可用;
  • count:统计元素出现的次数;对于自定义类型需要对==号进行重载;
#include<iostream>
using namespace std;
#include<vector>
#include<algorithm>

class Person {
public:
	Person(string name,int age) {
		this->m_Name = name;
		this->m_Age = age;
	}

	bool operator==(const Person& p) {
		return this->m_Age == p.m_Age;
	}

	string m_Name;
	int m_Age;
};

void test01() {
	vector<Person> v;
	Person p1("刘备", 35);
	Person p2("关羽", 30);
	Person p3("张飞", 29);
	Person p4("赵云", 25);
	Person p5("黄忠", 50);
	Person p6("马超", 22);
	v.push_back(p1);
	v.push_back(p2);
	v.push_back(p3);
	v.push_back(p4);
	v.push_back(p5);
	v.push_back(p6);

	Person p7("诸葛亮", 25);
	int num = count(v.begin(), v.end(), p7);
	cout << "num = " << num << endl;
}

int main() {
	test01();
	system("pause");
	return 0;
}
  • count_if:按条件统计元素出现的次数,返回次数;

27.3.排序

  • sort:对容器内元素进行排序
    • sort属于开发中最常用的算法之一,需熟练掌握
#include<iostream>
using namespace std;
#include<algorithm>
#include<vector>

void myPrint(int val) {
	cout << val << " ";
}
void test01() {
	vector<int> v;
	v.push_back(10);
	v.push_back(50);
	v.push_back(30);
	v.push_back(20);
	v.push_back(60);

	sort(v.begin(), v.end());//sort默认是从小到大排序
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;

	sort(v.begin(), v.end(), greater<int>());//从大到小排序
	for_each(v.begin(), v.end(), myPrint);
	cout << endl;
}

int main() {
	test01();
	system("pause");
	return 0;
}
  • random_shuffle:指定范围内的元素随机调整次序(利用洗牌算法,打乱顺序。该算法比较实用,使用时记得加随机算法种子)
  • merge:容器元素合并,并存储到另一个容器中
    • 注意:两个容器必须是有序的
  • reverse:反转指定范围的元素(目标:面试中若到该算法,需要能快速想起来)

27.4.拷贝

  • copy:容器内指定范围的元素拷贝到另一个容器中,参数(1,起始,2,结束,3,起始)

27.5.替换

  • replace:将容器内指定范围的旧元素修改为新元素,参数(1,起始,2,结束,3,旧,4,新)
  • replace_if:容器内指定范围满足条件的元素替换为新元素,参数(1,起始,2,结束,3,谓词,4,新)
  • swap:互换两个容器的元素,参数(1,容器一,2,容器二)

27.6.算数生成

  • accumulate:计算区间内容器元素累计总和(很实用的算法),参数(1,起始,2,结束,3,起始值)
  • fill:向容器指定区间添加元素,参数(1,起始,2,结束,3,填充的值)

27.7.集合

  • set_intersection:求两个容器的交集,参数(1,起始一,2,结束一,3,起始二,4,结束二,5,起始三)

    • 求交集的两个集合必须是有序序列
    • 目标容器开辟空间需要从两个容器中取最小值
    • set_intersection,返回值既是交集中最后一个元素的位置
  • set_union:求两个容器的并集,参数同上

    • 两个集合必须是有序序列
  • set_difference:求两个容器的差集,参数同上

    • 两个集合必须是有序序列

28.演讲比赛流程管理系统

  • done

29.机房预约系统

  • Visual Studio C/C++代码读取文件出现乱码(文件保存时默认UTF-8,而控制台的编码方式为ANSI)
  • Identity*& manager 表示:manager 是一个指向 Identity 类对象的指针的引用。通过这个引用,可以直接修改传入的指针本身(而不仅仅是修改指针指向的对象);
  • done
    机房预约系统
0

评论区