基础语法 变量
变量的作用是方便操作内存
常量
作用:记录程序中不可修改的数据
c++定义常量的两种方式: 1. #define 宏常量 #define 常量名 常量值 2. const 修饰变量 const 数据类型 常量名 = 常量值
关键字
作用:关键字是C++中预先保留的单词(标识符)
在定义变量名,常量名的时候,就不要用这些名字了
标识符命名规则
作用:c++规定,给标识符(变量,常量)命名时,有一套自己的规则(命名规则)
数据类型
数据类型存在意义: 给变量分配合适的内存空间
整形(short, int, long, long long)
sizeof 关键字
实型(浮点型)
整数部分,和小数部分,都算有效数字,比如
3.14,是3位有效数字
double比float更加精准一些
float f1 = 3.14f ; double f2 = 3.1415926 ;
另外小数还有科学计数法的表示方法:
float f2 = 3e2 ;float f3 = 3e-2 ;
字符型
字符型变量 对应 ASCII 编码:
a - 97 A - 67
>cout << (int )ch << endl ;
转义字符
字符串型
作用: 用于表示一串字符
两种风格
C风格字符串: char 变量名[] = "xxxxx"
C++风格字符串: string 变量名 = "xxxxxx"
需要#include<string>
char str1[] = "123456789" ;
注意:str1的占内存字节数为里面的字符数+1,因为还有一个\0,所以sizeof(str1) = 10
布尔类型bool
数据的输入
int a = 0 ;int b = 0 ;cin >> a>>b;cout << a <<endl <<b<< endl ;
运算符
算数运算符
赋值运算符
比较运算符
int a = 9 ;int b = 9 ;cout << (a == b) << endl ;
逻辑运算符
程序流程结构
顺序结构
选择结构
if
switch(变量){case 1: xxx;break;}
循环结构
while(循环条件){}
do{循环语句} while(循环条件);
for
跳转语句
数组
一维数组 定义方式
char str[4 ] = { 'a' , 'b' , 'c' ,'\0' };cout << sizeof (str) << endl ; cout << str << endl ;
char str[4 ] = { 'a' , 'b' , 'c' ,'\0' };cout << sizeof (str) << endl ;cout << str << endl ;char str2[] = { 'a' , 'b' , 'c' };cout << sizeof (str2) << endl ;cout << str2 << endl ;char str3[] = "abc" ;cout << sizeof (str3) << endl ;cout << str3 << endl ;int arr[] = { 1 ,2 ,3 };cout << sizeof (arr) << endl ;cout << sizeof (arr) / sizeof (arr[0 ]) << endl ;cout << arr << endl ;
4 abc 3 abc烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫 4 abc 12 3 0000006B F9CFF798
注意,string是一个类,它不是单纯的字符数组,里面估计是有动态分配的,然后用指针,所以sizeof无论如何都是40
数组名
冒泡排序 二维数组 定义方式
二维数组,就是在一维数组上,多加一个维度。
数组名
char str[][4 ] = { {'1' , '2' , 'c' , '\0' }, {'a' , 'c' , 'b' , '\0' }}; cout << sizeof (str) << endl ;cout << (int )(str[0 ]) << endl ;cout << (int )(str[1 ]) << endl ;
函数
定义 函数的定义,分为5个步骤:
返回值类型
函数名
参数表列
函数体语句
return 表达式
调用
值传递
函数常见样式
这里的返回,是指的返回值,不是LR的返回
函数声明
函数的分文件编写
指针 说白了,就是内存地址。
指针变量的定义和使用
指针所占内存空间 地址多少个字节,指针就是多少个字节
32位系统,指针就是4个字节
64位系统,指针就是8个字节
空指针,野指针 int *p = NULL;
但是这个是不能进行*访问的,因为0-255之间的内存地址,是系统占用的,无法访问
总结 :空指针,野指针,都不是我们申请的空间,因此不要访问。
const修饰指针
1. const修饰指针:常量指针
int a = 10 ;int b = 20 ;const int * p;p = &a; p = &b; cout << *p << endl ; b = 30 ; cout << *p << endl ; *p = 40 ;
2. const修饰常量:指针常量
int a = 10 ;int b = 20 ;int * const p = &a; *p = 20 ; cout << *p << endl ;
3. const既修饰指针,也修饰常量
const int * const p = &a;
既不可以重定向,也不可以通过指针改值
指针和数组
指针和数组的配合使用
int arr[] = { 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 };int * p = arr;for (int i = 0 ; i < 10 ; i++) { cout << *p << endl ; p++; }
指针和函数
这里主要讲的是指针作为参数传参
指针,数组,函数 这里就是上面两个的综合使用了。
结构体 结构体属于自定义的数据类型,允许用户存储不同的数据类型
定义和使用
struct student { string name; int age; int score; } s3; int main () { struct student s1 ; s1.age = 12 ; s1.name = "test" ; s1.score = 30 ; cout << "s1 = " << s1.name << s1.age << s1.score << endl ; struct student s2 = { "liangji" , 12 , 13 }; cout << "s2 = " << s2.name << s2.age << s2.score << endl ; return 0 ; }
结构体数组
结构体指针 struct student { }; struct student * p = &s1;
结构体嵌套结构体
结构体大小 struct student { }; struct student { char name; }; struct student { char name; char name2; }; struct student { int a; }; struct student { char b; int a; };
结构体做函数参数 比较简单,没什么好说的
结构体中const使用场景
用常量指针,你无法通过函数传入的指针,来修改内存。好处是可以防止误操作
程序的内存模型
内存4区 和链接脚本里面的.text, .rodata, .data, .bss有什么关系?为什么有全局区?
C++ 内存分区 对应链接脚本段 存放内容 生命周期 代码区 .text 函数体的二进制代码 程序运行期间 全局区 .data、.bss、.rodata 全局变量、静态变量、字符串常量等 程序启动到结束 栈区 栈段(Stack) 函数参数、局部变量 函数调用期间 堆区 堆段(Heap) 动态分配的内存(new/malloc ) 手动分配 / 释放
代码区 .text
全局区 .rodata .data .bss
全局变量,静态变量,
常量:
字符串常量:全局区
const修饰全局变量:全局区
const修饰局部变量:栈区
栈区 stack中
局部变量,形参
堆区 heap
程序员自己申请,c++中,利用new,返回的是指针
p指针还是在栈中,因为是局部变量,但是里面的地址,指向的是堆区的int(10);
总结
new运算符
这里主要学习new的语法,以及在堆区开辟一个数组。
int * p = new int (10 );delete p; int * arr = new int [10 ]; delete[] arr;
引用 基本使用
注意事项
一上来就要告诉这个别名是谁的别名 一旦引用初始化后,就不可以改成别的变量的别名
引用做函数参数
就是当指针来用,可以省掉指针的繁琐操作。
引用做函数返回值
int & func () { int a = 10 ; return a; } int main () { int & b = func(); cout << b << endl ; cout << b << endl ; return 0 ; }
返回的是局部变量的引用,所以,最终输出,只会有第一次是正确的,因为编译器会做保留,但是这个和版本有关,新版本里面,会一直做保留。
所以尽量不要让函数返回局部变量的引用。因为这块内存会被释放
正确的写法是下面这样。
int & func () { static int a = 10 ; return a; } int main () { int & b = func(); cout << b << endl ; cout << b << endl ; return 0 ; }
还有一种用法,是把返回值为引用的函数,作为左值,这样也能修改对应内存, 相当于省掉一个中间别名变量。
int & func () { static int a = 10 ; return a; } int main () { int & b = func(); cout << b << endl ; cout << b << endl ; func() = 1000 ; cout << b << endl ; return 0 ; }
引用的本质
常量引用
用来修饰函数的形参,防止误操作,就像常量指针一样,防止你修改
const int & b = 10 ; cout << b << endl ;
void func (const int & temp) { } int a = 10 ;func(a);
函数的提高 函数默认参数
实验看下来,必须是靠左的优先判定缺省。
void func (int a,int b = 20 , int c = 30 ) { cout << a + b + c << endl ; } int main () { func(2 ,2 ); return 0 ; }
注意事项1 :如果某个位置已经有了默认参数,那么从这个位置往后,从左到右,都必须有默认值
注意事项2 :如果函数声明有默认参数,函数实现就不能有默认参数了。 声明和实现,只能有一个有默认参数
函数占位参数
说白了,就是保留一个形参传参,但是函数里面还没有使用,就叫占位。
函数重载 概述
同作用域(你放在main外面,都是全局作用域)
同名函数
函数参数列表不同。(不包含返回值作为条件。)
注意事项 1. 引用作为函数重载条件
void func (int & a) { cout << "func1()" << endl ; } void func (const int & a) { cout << "func2()" << endl ; } int main () { int a = 10 ; func(a); func(10 ); return 0 ; }
const修饰引用,也会发生函数参数列表不同,发生重载。
传参调用哪一个: func(10), 如果是第一个函数,那么会发生:int & a = 10, 语法不通过 对于第二个函数,const int & a =10, 则可以通过,所以func(10)只会走const int & a的函数。
而对于func(a),两个都可以,编译器默认走简单的。
2. 函数重载碰到默认参数
void func (int a) { cout << "func1 " << a << endl ; } void func (int a = 10 ) { cout << "func2 " << a << endl ; } int main () { int a = 11 ; func(a); return 0 ; }
类和对象 c++面向对象的三大特性:
c++认为万物皆可为对象,对象上有其属性和行为
封装 为什么要封装 权限:
public: 公共
protected: 保护
类内可以访问,类外不可以访问
(和private的区别体现在继承上)
子可以访问父
private: 私有
struct和class区别
成员属性设置为私有
对象的初始化和清理 就是对象的出厂设置。
创建对象,希望他有一个自动初始化的动作
释放对象,希望他有一个自动释放内部资源的动作
构造函数和析构函数
这两个函数是编译器自动调用的,来完成对象初始化和清理工作。
就算我们不提供,编译器也会自动补充
函数语法
class person {public: person() { cout << "construct fun1" << endl ; } ~person() { cout << "destruct fun" << endl ; } }; int main () { person p; return 0 ; }
构造函数的分类及调用
先说分类 class person { public: person() { } person(int a) { } person(int a){ } person(const person &p){ this->age = p.age } ~person() { } };
调用 person p; person p2 (10 ) ; person p3 (p2) ; person p1; person p2 = person(10 ); person p3 = person(p1); person p4 = 10 ; person p4 = p5;
拷贝构造函数调用时机 什么时候会用到拷贝构造函数
person p1 (20 ) ; person p2 (p1) ; void dowork (person p) {} person p; dowork(p); void dowork () {person p1; return p1}person p3 = dowork();
构造函数 默认创建规则
深拷贝与浅拷贝 就是拷贝成员变量(指针,指向一段内存)
浅拷贝:只拷贝指针,实际还是指向同一块内存 深拷贝:不仅拷贝指针,同时也拷贝一块内存。
初始化列表
想要初始化对象,除了构造函数内部初始化,还有初始化列表的形式
class person {public: person(): age(1 ), name("liangji" ) { } person(int a, string b): age(a), name(b) { } };
类对象作为成员变量 class A { };class B {private: A; };
当创建B对象的时候,现有B还是现有A,构造函数的先后顺序
静态成员 static xxx
class person {public: static int a; }; int person::a = 1 ;
静态成员变量
静态成员函数(static 成员函数)是 C++ 面向对象编程中非常实用的特性,它的核心价值在于不依赖对象 就能访问类的静态资源 ,同时能封装和类相关、但和具体对象无关的逻辑
class person {public: static void func () { a = 1 ; } static int a; }; int person::a = 1 ;person p; p.func(); person::func();
c++对象模型和this指针 成员变量 和 成员函数 分开存储
这里说的是类对象的内存分布,只有非静态成员变量,才是一个类对象上面(栈),静态成员变量在RW段,成员函数属于代码段
class person {}p; sizeof (p) == 1
class person { int a; }p; sizeof (p) == 4 ;
class person { static int b; }p; int person::b =10 ;sizeof (p) == 1 ;
class person { void func () {}; }p; sizeof (p) == 1 ;
class person { static void func () {}; }p; sizeof (p) == 1 ;
this指针概念 this指针,指向对象的那一块内存的指针
用处之一就是链式编程思想
class person {public: int a; person& add (const person & b) { return *this; } }; person p1 (10 ) ; person p2 (10 ) ; person p3 = p2.add(p1).add(p1).add(p1);
空指针访问成员函数
class person {public: void func () { }; void func2 () { if (this == NULL ) { return ; } } }; person* p = NULL ; p->func();
const修饰成员函数
常函数
成员函数后面+ const,表示不能修改成员变量
如果在还要修改,只能在成员变量前面+mutable(表示常函数也能修改)
常对象
const + 声明对象(类对象)
常对象只能调用常函数
常函数
class person {public: int a; mutable int b; void func1 () { } void func2 () const { } };
常对象
const person p; p.b = 10 ; p.func1(); p.func2();
友元
说白了,友元,就是在类的私有域里面,事先声明好谁还有权限来访问
1. 全局函数做友元
class person { friend void goodgay (person * p) ; private: int a; public: int b; }; void goodgay (person * p) { cout << p->a << endl ; } person p; goodgay(&p);
类做友元
B 类声明为 A 类友元,B 类可直接访问 A 类的私有成员
#include <iostream> using namespace std ; class FriendClass ;class MyClass { friend class FriendClass ; private: int private_num = 99 ; }; class FriendClass {public: void accessPrivate (MyClass& obj) { cout << "访问到私有成员:" << obj.private_num << endl ; } }; int main () { MyClass mc; FriendClass fc; fc.accessPrivate(mc); return 0 ; }
成员函数做友元
只允许指定的某个成员函数访问私有成员,而非整个类的所有函数
核心:只声明FriendClass的accessPrivate函数为友元
#include <iostream> using namespace std ; class MyClass ;class FriendClass {public: void accessPrivate (MyClass& obj) ; }; class MyClass {private: int private_num = 88 ; friend void FriendClass::accessPrivate (MyClass& obj) ; }; void FriendClass::accessPrivate (MyClass& obj) { cout << "成员函数访问私有成员:" << obj.private_num << endl ; } int main () { MyClass mc; FriendClass fc; fc.accessPrivate(mc); return 0 ; }
运算符重载 主要有两个地方:
作为类的成员函数(写在类内部)
作为全局函数(写在类外部,通常需要友元)
+ 重载
核心:实现两个自定义对象的加法 ,通常用成员函数或全局友元函数实现
class Point {public: int x, y; Point(int x=0 , int y=0 ) : x(x), y(y) {} Point operator+(const Point& other) { return Point(x + other.x, y + other.y); } }; int main () { Point a (1 ,2 ) , b (3 ,4 ) ; Point c = a + b; cout << c.x << "," << c.y << endl ; return 0 ; }
<< 左移运算符 重载
核心:用于自定义对象的cout输出 ,必须是全局友元函数 (因为左操作数是ostream)
class Point {public: int x, y; Point(int x=0 , int y=0 ) : x(x), y(y) {} friend ostream& operator<<(ostream& os, const Point& p); }; ostream& operator<<(ostream& os, const Point& p) { os << "(" << p.x << "," << p.y << ")" ; return os; } int main () { Point p (2 ,3 ) ; cout << p << endl ; return 0 ; }
++ 递增运算符 重载
核心:区分前置 ++(返回引用)和后置 ++(返回值,用int占位)
class Counter {public: int n; Counter(int n=0 ) : n(n) {} Counter& operator++() { ++n; return *this; } Counter operator++(int ) { Counter temp = *this; ++n; return temp; } }; int main () { Counter c (5 ) ; cout << (c++).n << endl ; cout << (++c).n << endl ; return 0 ; }
= 赋值运算符 重载
核心:默认浅拷贝可能导致内存泄漏 / 重复释放,需手动实现深拷贝(这里用简单示例演示语法)
class Data {public: int * p; Data(int val) { p = new int (val); } ~Data() { delete p; } Data& operator=(const Data& other) { if (this == &other) return *this; delete p; p = new int (*other.p); return *this; } }; int main () { Data a (10 ) , b (20 ) ; a = b; cout << *a.p << endl ; return 0 ; }
关系运算符 == < > 重载
核心:返回bool,用于对象之间的比较
class Point {public: int x, y; Point(int x=0 , int y=0 ) : x(x), y(y) {} bool operator==(const Point& other) { return x == other.x && y == other.y; } bool operator<(const Point& other) { return x < other.x || (x == other.x && y < other.y); } }; int main () { Point a (1 ,2 ) , b (1 ,2 ) , c (2 ,3 ) ; cout << (a == b) << endl ; cout << (a < c) << endl ; return 0 ; }
() 函数调用运算符 重载
核心:让类对象可以像函数一样调用,也叫仿函数
class Add {public: int operator () (int a, int b) { return a + b; } }; int main () { Add add; int res = add(3 , 5 ); cout << res << endl ; return 0 ; }
继承 基本语法
class Base {public: int m_A; protected: int m_B; private: int m_C; }; class Son : public Base {public: int m_D; };
class 派生类 : 继承方式 基类1 , 继承方式 基类2 , ... { };
继承的3种方式
class Base {public: int pub; protected: int pro; private: int pri; }; class PublicDerived : public Base { void func () { pub = 1 ; pro = 2 ; } }; class ProtectedDerived : protected Base { void func () { pub = 1 ; pro = 2 ; } }; class PrivateDerived : private Base { void func () { pub = 1 ; pro = 2 ; } };
继承中的对象模型 派生类对象会完整包含基类的成员变量,并在内存中按 “基类部分 → 派生类部分” 的顺序排列。
class Base {public: int m_A; }; class Son : public Base {public: int m_B; }; int main () { Son s; s.m_A = 10 ; s.m_B = 20 ; return 0 ; }
继承中构造和析构顺序
构造顺序:先调用基类构造函数,再调用派生类构造函数
析构顺序:先调用派生类析构函数,再调用基类析构函数(与构造顺序相反)
class Base {public: Base() { cout << "Base 构造" << endl ; } ~Base() { cout << "Base 析构" << endl ; } }; class Son : public Base {public: Son() { cout << "Son 构造" << endl ; } ~Son() { cout << "Son 析构" << endl ; } }; int main () { Son s; return 0 ; }
若基类有带参构造函数,派生类必须在初始化列表中显式调用基类构造:
class Base {public: Base(int a) : m_A(a) {} private: int m_A; }; class Son : public Base {public: Son(int a, int b) : Base(a), m_B(b) {} private: int m_B; };
继承同名成员处理方式 当派生类与基类有同名成员时,默认访问派生类成员;若要访问基类成员,需加基类作用域 Base::
class Base {public: int m_A = 10 ; }; class Son : public Base {public: int m_A = 20 ; void func () { cout << m_A << endl ; cout << Base::m_A << endl ; } };
class Base {public: void func () { cout << "Base::func()" << endl ; } }; class Son : public Base {public: void func () { cout << "Son::func()" << endl ; } void callBaseFunc () { func(); Base::func(); } };
注意:派生类的同名函数会隐藏基类的所有同名函数(包括重载版本),若想继承基类重载函数,可使用 using Base::func; 声明
继承同名静态成员处理方式 静态成员的处理与非静态成员类似,只是访问方式多了类名访问:
多继承语法(一子多父) 一个子类,同时继承多个父类
class 派生类 : 继承方式 基类1 , 继承方式 基类2 , ... { ... };
class Base1 {public: int m_A = 10 ; }; class Base2 {public: int m_B = 20 ; }; class Son : public Base1, public Base2 {public: int m_C = 30 ; }; int main () { Son s; cout << s.m_A << endl ; cout << s.m_B << endl ; cout << s.m_C << endl ; return 0 ; }
多继承可能导致成员名冲突,需用 Base1::m_A 明确指定
菱形继承(虚继承) 菱形继承指:两个派生类继承同一个基类 ,又有一个类同时继承这两个派生类 ,导致基类成员被重复继承,引发数据冗余与二义性
class Animal {public: int m_Age; }; class Sheep : public Animal {};class Tuo : public Animal {};class SheepTuo : public Sheep, public Tuo {};int main () { SheepTuo st; st.Sheep::m_Age = 10 ; st.Tuo::m_Age = 20 ; return 0 ; }
解决方案:虚继承
使用 virtual 关键字修饰继承,使基类变为虚基类,只保留一份成员:
class Animal {public: int m_Age; }; class Sheep : virtual public Animal {};class Tuo : virtual public Animal {};class SheepTuo : public Sheep, public Tuo {};int main () { SheepTuo st; st.m_Age = 10 ; cout << st.m_Age << endl ; return 0 ; }
多态 多态是面向对象编程的核心特性 之一,核心思想是“同一接口,多种实现 ”,即基类指针 / 引用可以根据实际指向的派生类对象 ,调用对应的函数实现 。
多态的基本概念 多态的分类
静态多态(编译期多态) :函数重载、运算符重载,在编译阶段确定函数地址。
动态多态(运行期多态) :通过虚函数实现,在运行阶段根据对象实际类型确定函数地址。
动态多态的满足条件
继承关系 :必须存在基类与派生类。
虚函数重写 :派生类重写基类的虚函数(函数名、参数列表、返回值类型完全一致,C++11 后协变返回类型也允许)。
基类指针 / 引用调用 :通过基类指针或引用调用虚函数。
核心就是靠基类指针,来指向子类对象,然后调用基类虚函数,来实际运行子类的实现方法,实际运行中,这样就实现了靠指针指谁用谁 ,接口不变,龙生九子,各有不同,但接口名称一样,指针指谁用谁
class Base {public: virtual void func () { cout << "Base::func()" << endl ; } }; class Derived : public Base {public: void func () override { cout << "Derived::func()" << endl ; } }; int main () { Base* p = new Derived(); p->func(); delete p; return 0 ; }
人的工作视角就是基类,因为我只关心基类这个东西,子类只是用来实现一些方法的。比如我的一个解法,我就可以自己写一个子类,然后重写里面的solution()方法 。这样就可以通过基类指针来选择调用我的子类实现。
#include <iostream> using namespace std ; class base {protected: int a; int b; public: base(int x, int y) :a(x), b(y) { cout << "base construction" << endl ; } virtual ~base() { cout << "base析构" << endl ; } virtual void cal () { cout << "a = " << a << " b = " << b << endl ; cout << "this is base, not real calculator" << endl ; } }; class add_cal : public base{public: add_cal(int x, int y) : base(x, y) { cout << "add_cal construction" << endl ; } ~add_cal() override { cout << "add_cal 析构" << endl ; } void cal () override { cout << "a + b = " << a + b << endl ; } }; class mul_cal : public base {public: mul_cal(int x, int y) : base(x, y) { cout << "mul_cal construction" << endl ; } ~mul_cal() override { cout << "mul_cal 析构" << endl ; } void cal () override { cout << "a * b = " << a * b << endl ; } }; int main () { base* p = new add_cal(1 , 2 ); p->cal(); delete p; p = new mul_cal(2 , 3 ); p->cal(); delete p; return 0 ; }
纯虚函数 和 抽象类 (开始定义接口类 了) 纯虚函数 :
语法:virtual 返回值类型 函数名(参数列表) = 0;
特点:没有函数体,必须在派生类中重写 ,否则派生类也为抽象类。
抽象类
包含至少一个纯虚函数的类称为抽象类。
特点:无法实例化对象 ,只能作为接口被继承,强制派生类实现纯虚函数
说白了,抽象类 彻底变成一个接口类
class AbstractClass {public: virtual void pureFunc () = 0 ; virtual void normalFunc () { cout << "Base normalFunc()" << endl ; } }; class ConcreteClass : public AbstractClass {public: void pureFunc () override { cout << "ConcreteClass pureFunc()" << endl ; } }; int main () { AbstractClass* p = new ConcreteClass(); p->pureFunc(); p->normalFunc(); delete p; return 0 ; }
虚析构和纯虚析构
这里说白了就是,必须强制让基类指针指向的子类对象释放的时候,强制调用子类实现的析构函数,不然会自动调用基类的析构函数(因为是基类指针)
(virtual只是一个重定向的过程。)
基类 ,定义基本的抽象的接口需要的功能,虚函数virtual来让基类指针自动重定向内部函数的调用。(linux的gpio子系统)
子类A ,是A实现的接口,A厂的原厂驱动实现子类B ,B厂的原厂驱动实现
在多态场景下,基类指针指向派生类对象时 ,若基类析构函数不是虚函数,会导致派生类对象无法被正确释放 ,造成内存泄漏。
虚析构函数
语法:virtual ~类名() {}
作用:保证基类指针删除时,能正确调用派生类析构函数。 纯虚析构函数
语法:virtual ~类名() = 0;
特点:必须在类外提供实现,否则链接错误;包含纯虚析构的类也是抽象类
class Base {public: Base() { cout << "Base 构造" << endl ; } virtual ~Base() { cout << "Base 析构" << endl ; } }; class Derived : public Base {public: Derived() { cout << "Derived 构造" << endl ; } ~Derived() override { cout << "Derived 析构" << endl ; } }; int main () { Base* p = new Derived(); delete p; return 0 ; }
纯虚析构函数注意事项
class AbstractBase {public: virtual ~AbstractBase() = 0 ; }; AbstractBase::~AbstractBase() {}
c++文件操作 C++ 通过文件流类 (fstream 家族)实现文件读写,文件分为文本文件 和二进制文件 两种类型
文本文件 文本文件以字符形式存储数据,人类可读 ,适合存储字符串、数字等文本内容
#include <fstream> #include <iostream> using namespace std ;
写文件 步骤:
#include <fstream> using namespace std ; int main () { ofstream ofs; ofs.open("test.txt" , ios::out); ofs << "姓名:张三" << endl ; ofs << "年龄:18" << endl ; ofs << "性别:男" << endl ; ofs.close(); return 0 ; }
文件打开模式:
读文件 步骤:
#include <fstream> #include <string> using namespace std ; int main () { ifstream ifs; ifs.open("test.txt" , ios::in); if (!ifs.is_open()) { cout << "文件打开失败!" << endl ; return 1 ; } char c; while ((c = ifs.get()) != EOF) { cout << c; } char buf[1024 ] = {0 }; while (ifs.getline(buf, sizeof (buf))) { cout << buf << endl ; } string line; while (getline(ifs, line)) { cout << line << endl ; } char buf2[1024 ] = {0 }; ifs.read(buf2, sizeof (buf2)); cout << buf2 << endl ; ifs.close(); return 0 ; }
二进制文件 二进制文件 以字节 形式存储数据,不可直接阅读 ,适合存储自定义数据类型(如类、结构体),读写速度更快、数据更紧凑。
写二进制文件
#include <fstream> using namespace std ; class Person {public: char m_Name[64 ]; int m_Age; }; int main () { ofstream ofs; ofs.open("person.bin" , ios::out | ios::binary); Person p = {"张三" , 18 }; ofs.write((const char *)&p, sizeof (p)); ofs.close(); return 0 ; }
读二进制文件
#include <fstream> using namespace std ; class Person {public: char m_Name[64 ]; int m_Age; }; int main () { ifstream ifs; ifs.open("person.bin" , ios::in | ios::binary); if (!ifs.is_open()) { cout << "文件打开失败!" << endl ; return 1 ; } Person p; ifs.read((char *)&p, sizeof (p)); cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl ; ifs.close(); return 0 ; }
总结
辨析
模板 (泛型编程) 模板是 C++ 实现泛型编程 的核心机制,核心思想是“一套代码,多种数据类型 ”,通过参数化类型 实现代码复用,避免为每种类型重复编写逻辑
模板的概念
函数模板 基本语法
注意事项
普通函数 vs 函数模板
调用优先级规则
若普通函数和函数模板都可匹配,优先调用普通函数 。
可通过 mySwap<>(a, b) 强制调用模板 。
若模板能生成更匹配的版本,则调用模板。】
void mySwap (int & a, int & b) { ... } template <typename T> void mySwap (T& a, T& b) { ... } mySwap(1 , 2 ); mySwap(1.0 , 2.0 );
局限性 模板无法处理所有类型 , 比如自定义的类型之间的运算需要自己重载运算符
类模板 基础语法
类模板 vs 函数模板
类模板对象做函数参数
类模板与继承
子类继承类模板时,必须指定父类的模板类型 ,或让子类也成为模板
类模板成员函数类外实现 需在类外 声明模板参数列表
说白了,一次声明模板参数,只能用一次
类模板分文件编写
类模板与友元 全局友元函数 需在类内声明 、类外实现 ,并提前声明模板
注意必须提前声明,步骤如下:
提前声明模板类 ,声明模板函数 operator<<
类内声明友元,模板函数operator<< (因为外面可以靠template来表示下面是模板,类内没办法,所以编译器只能用operator<< 来告诉编译器,他是个模板函数)
类外实现模板函数operator<<
demo: 实现一个数组类的封装 实现一个通用数组类,支持:
动态扩容
尾插、尾删
下标访问
打印输出
深拷贝(避免内存泄漏)
STL 定位
基本概念
容器(Container) :管理一组元素的集合(如 vector、list、map)。
算法(Algorithm) :操作容器元素 的通用函数 (如 sort、find、for_each)。
迭代器(Iterator) :连接容器与算法的 “指针 ”,提供统一的遍历方式 。
核心思想:容器与算法通过迭代器解耦,一套算法可适配多种容器。
STL 六大组件
容器,迭代器,算法的关系
STL 常用容器 string 容器(字符串) string 是管理字符序列 的容器,替代 C 风格 char*,更安全易用。
#include <iostream> #include <string> using namespace std ; int main () { string s1; string s2 ("hello" ) ; string s3 (5 , 'a' ) ; string s4 (s2) ; cout << "=== 构造结果 ===" << endl ; cout << "s2: " << s2 << endl ; cout << "s3: " << s3 << endl ; cout << "s4: " << s4 << endl ; s1 = "world" ; s1.assign("abc" ); cout << "\n=== 赋值结果 ===" << endl ; cout << "s1: " << s1 << endl ; s1 += s2; s1.append("123" ); cout << "\n=== 拼接结果 ===" << endl ; cout << "s1: " << s1 << endl ; size_t pos = s1.find("hello" ); cout << "\n=== 查找结果 ===" << endl ; if (pos != string ::npos) { cout << "\"hello\" 位置: " << pos << endl ; } else { cout << "未找到" << endl ; } s1.replace(3 , 5 , "xyz" ); cout << "\n=== 替换结果 ===" << endl ; cout << "s1: " << s1 << endl ; int cmp_res = s1.compare("abcxyz123" ); cout << "\n=== 比较结果 ===" << endl ; if (cmp_res == 0 ) { cout << "s1 等于 \"abcxyz123\"" << endl ; } else if (cmp_res > 0 ) { cout << "s1 大于 \"abcxyz123\"" << endl ; } else { cout << "s1 小于 \"abcxyz123\"" << endl ; } cout << "\n=== 存取结果 ===" << endl ; cout << "s1[0]: " << s1[0 ] << endl ; cout << "s1.at(0): " << s1.at(0 ) << endl ; try { } catch (out_of_range& e) { cout << "越界异常: " << e.what() << endl ; } s1.insert(1 , "ins" ); cout << "\n=== 插入结果 ===" << endl ; cout << "s1: " << s1 << endl ; s1.erase(2 , 3 ); cout << "\n=== 删除结果 ===" << endl ; cout << "s1: " << s1 << endl ; string sub = s1.substr(1 , 3 ); cout << "\n=== 子串结果 ===" << endl ; cout << "sub: " << sub << endl ; return 0 ; }
=== 构造结果 === s2: hello s3: aaaaa s4: hello === 赋值结果 === s1: abc === 拼接结果 === s1: abchello123 === 查找结果 === "hello" 位置: 3 === 替换结果 === s1: abcxyz123 === 比较结果 === s1 等于 "abcxyz123" === 存取结果 === s1[0 ]: a s1.at(0 ): a === 插入结果 === s1: ainsbcxyz123 === 删除结果 === s1: aibcxyz123 === 子串结果 === sub: ibc
注意,.at(), .replace(), .erase(), 这些不是迭代器,而是容器类的成员函数
vector 容器(动态数组) vector 是单端动态数组 ,支持随机访问 ,尾部插入 / 删除高效。
#include <iostream> #include <vector> using namespace std ; int main () { vector <int > v1; vector <int > v2 (5 , 10 ) ; vector <int > v3 (v2.begin(), v2.end()) ; vector <int > v4 (v2) ; cout << "=== 构造结果 ===" << endl ; cout << "v2: " ; for (int x : v2) cout << x << " " ; cout << endl ; v1 = v2; vector <int > v5; v5.assign(v2.begin(), v2.end()); v5.assign(3 , 20 ); cout << "\n=== 赋值结果 ===" << endl ; cout << "v1: " ; for (int x : v1) cout << x << " " ; cout << "\nv5: " ; for (int x : v5) cout << x << " " ; cout << endl ; cout << "\n=== 容量与大小 ===" << endl ; cout << "v2.size() = " << v2.size() << endl ; cout << "v2.capacity() = " << v2.capacity() << endl ; cout << "v2.empty() = " << v2.empty() << endl ; v1.push_back(1 ); v1.pop_back(); v1.insert(v1.begin(), 2 ); v1.erase(v1.begin()); cout << "\n=== 插入/删除结果 ===" << endl ; cout << "v1: " ; for (int x : v1) cout << x << " " ; cout << endl ; cout << "\n=== 存取结果 ===" << endl ; cout << "v2[0] = " << v2[0 ] << endl ; cout << "v2.at(0) = " << v2.at(0 ) << endl ; cout << "v2.front() = " << v2.front() << endl ; cout << "v2.back() = " << v2.back() << endl ; try { } catch (out_of_range& e) { cout << "越界异常: " << e.what() << endl ; } v1.swap(v5); cout << "\n=== 互换结果 ===" << endl ; cout << "v1: " ; for (int x : v1) cout << x << " " ; cout << "\nv5: " ; for (int x : v5) cout << x << " " ; cout << endl ; vector <int > v6; v6.reserve(100 ); cout << "\n=== 预留空间结果 ===" << endl ; cout << "v6.size() = " << v6.size() << endl ; cout << "v6.capacity() = " << v6.capacity() << endl ; return 0 ; }
=== 构造结果 === v2: 10 10 10 10 10 === 赋值结果 === v1: 10 10 10 10 10 v5: 20 20 20 === 容量与大小 === v2.size() = 5 v2.capacity() = 5 v2.empty() = 0 === 插入/删除结果 === v1: 10 10 10 10 10 === 存取结果 === v2[0 ] = 10 v2.at(0 ) = 10 v2.front() = 10 v2.back() = 10 === 互换结果 === v1: 20 20 20 v5: 10 10 10 10 10 === 预留空间结果 === v6.size() = 0 v6.capacity() = 100
所以迭代器的真正意义,在于无视底层容器的数据结构,统一遍历方式。
deque 容器(双端队列) deque 是双端动态数组 ,支持首尾快速插入 / 删除,同时支持随机访问。
stack 容器(栈) stack 是 后进先出(LIFO 的适配器容器,默认底层用 deque 实现 。
核心接口
stack <int > s;s.push(1 ); s.pop(); s.top(); s.empty(); s.size();
#include <iostream> #include <stack> using namespace std ; int main () { stack <int > s; s.push(10 ); s.push(20 ); s.push(30 ); cout << "=== 入栈后 ===" << endl ; cout << "栈顶元素:" << s.top() << endl ; cout << "元素个数:" << s.size() << endl ; cout << "是否为空:" << s.empty() << endl ; cout << "\n=== 出栈操作 ===" << endl ; while (!s.empty()) { cout << "弹出:" << s.top() << endl ; s.pop(); } cout << "出栈后元素个数:" << s.size() << endl ; cout << "是否为空:" << s.empty() << endl ; stack <int > s2; int arr[] = {1 , 2 , 3 , 4 , 5 }; for (int i = 0 ; i < 5 ; ++i) { s2.push(arr[i]); } cout << "\n=== 逆序输出(利用栈 LIFO 特性)===" << endl ; while (!s2.empty()) { cout << s2.top() << " " ; s2.pop(); } cout << endl ; return 0 ; }
=== 入栈后 === 栈顶元素:30 元素个数:3 是否为空:0 === 出栈操作 === 弹出:30 弹出:20 弹出:10 出栈后元素个数:0 是否为空:1 === 逆序输出(利用栈 LIFO 特性)=== 5 4 3 2 1
queue 容器(队列) queue 是 先进先出(FIFO) 的适配器容器,默认底层用 deque 实现。
queue <int > q;q.push(1 ); q.pop(); q.front(); q.back(); q.empty(); q.size();
#include <iostream> #include <queue> using namespace std ; int main () { queue <int > q; q.push(10 ); q.push(20 ); q.push(30 ); cout << "=== 入队后 ===" << endl ; cout << "队头元素:" << q.front() << endl ; cout << "队尾元素:" << q.back() << endl ; cout << "元素个数:" << q.size() << endl ; cout << "是否为空:" << q.empty() << endl ; cout << "\n=== 出队操作 ===" << endl ; while (!q.empty()) { cout << "弹出:" << q.front() << endl ; q.pop(); } cout << "出队后元素个数:" << q.size() << endl ; cout << "是否为空:" << q.empty() << endl ; queue <string > q2; q2.push("张三" ); q2.push("李四" ); q2.push("王五" ); cout << "\n=== 模拟排队叫号 ===" << endl ; while (!q2.empty()) { cout << "当前叫号:" << q2.front() << endl ; q2.pop(); } return 0 ; }
=== 入队后 === 队头元素:10 队尾元素:30 元素个数:3 是否为空:0 === 出队操作 === 弹出:10 弹出:20 弹出:30 出队后元素个数:0 是否为空:1 === 模拟排队叫号 === 当前叫号:张三 当前叫号:李四 当前叫号:王五
list 容器(双向链表) list 是双向链表 ,支持任意位置高效插入 / 删除 ,但不支持随机访问
#include <iostream> #include <list> #include <algorithm> using namespace std ; int main () { list <int > l; list <int > l2 (5 , 10 ) ; list <int > l3 (l2.begin(), l2.end()) ; list <int > l4 (l2) ; cout << "=== 构造结果 ===" << endl ; cout << "l2: " ; for (int x : l2) cout << x << " " ; cout << endl ; l = l2; list <int > l5; l5.assign(5 , 20 ); cout << "\n=== 赋值结果 ===" << endl ; cout << "l: " ; for (int x : l) cout << x << " " ; cout << "\nl5: " ; for (int x : l5) cout << x << " " ; cout << endl ; cout << "\n=== 大小操作 ===" << endl ; cout << "l.size() = " << l.size() << endl ; cout << "l.empty() = " << l.empty() << endl ; l.push_back(1 ); l.push_front(2 ); l.insert(l.begin(), 3 ); l.erase(l.begin()); cout << "\n=== 插入/删除结果 ===" << endl ; cout << "l: " ; for (int x : l) cout << x << " " ; cout << endl ; cout << "\n=== 存取结果 ===" << endl ; cout << "l.front() = " << l.front() << endl ; cout << "l.back() = " << l.back() << endl ; l.reverse(); cout << "\n=== 反转结果 ===" << endl ; cout << "l: " ; for (int x : l) cout << x << " " ; cout << endl ; l.sort(); cout << "\n=== 排序结果 ===" << endl ; cout << "l: " ; for (int x : l) cout << x << " " ; cout << endl ; auto it = l.begin(); ++it; l.insert(it, 100 ); cout << "\n=== 迭代器验证 ===" << endl ; cout << "原迭代器仍指向:" << *it << endl ; return 0 ; }
=== 构造结果 === l2: 10 10 10 10 10 === 赋值结果 === l: 10 10 10 10 10 l5: 20 20 20 20 20 === 大小操作 === l.size() = 5 l.empty() = 0 === 插入/删除结果 === l: 2 10 10 10 10 10 1 === 存取结果 === l.front() = 2 l.back() = 1 === 反转结果 === l: 1 10 10 10 10 10 2 === 排序结果 === l: 1 2 10 10 10 10 10 === 迭代器验证 === 原迭代器仍指向:2
set/multiset 容器(有序集合) set/multiset 是有序关联容器 ,底层为红黑树 ,元素自动排序 。
总的来说,无论什么样的数据结构,其类内部提供的方法,无非就是增删改查。
对于集合的排序,set<集合元素类型,排序用的比较规则>
这里的自定义排序规则,其实是,比较规则。告诉内置的排序算法,如何来比较自定义数据类型的大小关系。
#include <iostream> #include <set> using namespace std ; class Person {public: int age; string name; Person(string n, int a) : name(n), age(a) {} }; struct cmp { bool operator () (const Person& a, const Person& b) const { return a.age < b.age; } }; int main () { set <int > s; s.insert(3 ); s.insert(1 ); s.insert(4 ); s.insert(1 ); cout << "=== set 插入结果 ===" << endl ; for (int x : s) cout << x << " " ; cout << endl ; auto it = s.find(3 ); if (it != s.end()) { cout << "找到元素:" << *it << endl ; } else { cout << "未找到元素" << endl ; } cout << "元素 1 的个数:" << s.count(1 ) << endl ; s.erase(3 ); cout << "=== set 删除后 ===" << endl ; for (int x : s) cout << x << " " ; cout << endl ; cout << "size: " << s.size() << ", empty: " << s.empty() << endl ; multiset <int > ms; ms.insert(3 ); ms.insert(1 ); ms.insert(3 ); ms.insert(4 ); cout << "\n=== multiset 插入结果 ===" << endl ; for (int x : ms) cout << x << " " ; cout << endl ; cout << "元素 3 的个数:" << ms.count(3 ) << endl ; set <int , greater<int >> s_desc; s_desc.insert(3 ); s_desc.insert(1 ); s_desc.insert(4 ); cout << "\n=== set 降序排序 ===" << endl ; for (int x : s_desc) cout << x << " " ; cout << endl ; set <Person, cmp> s_person; s_person.insert(Person("张三" , 20 )); s_person.insert(Person("李四" , 18 )); s_person.insert(Person("王五" , 25 )); cout << "\n=== 自定义类型按 age 升序 ===" << endl ; for (const Person& p : s_person) { cout << p.name << "(" << p.age << ") " ; } cout << endl ; return 0 ; }
=== set 插入结果 === 1 3 4 找到元素:3 元素 1 的个数:1 === set 删除后 === 1 4 size: 2 , empty: 0 === multiset 插入结果 === 1 3 3 4 元素 3 的个数:2 === set 降序排序 === 4 3 1 === 自定义类型按 age 升序 === 李四(18 ) 张三(20 ) 王五(25 )
map/multimap 容器(键值对) map/multimap 是有序键值对 容器,底层为红黑树,按键自动排序
这是图中核心操作的完整演示,包含了 **map(键唯一)**和 **multimap(允许重复键)**的使用场景.
#include <iostream> #include <map> #include <string> using namespace std ; int main () { map <int , string > m; m.insert(pair <int , string >(1 , "one" )); m.insert(pair <int , string >(3 , "three" )); m[2 ] = "two" ; m[4 ] = "four" ; m.insert(pair <int , string >(1 , "New_One" )); cout << "=== map 插入结果(自动排序) ===" << endl ; for (auto & p : m) { cout << p.first << ": " << p.second << " " ; } cout << endl ; auto it = m.find(2 ); if (it != m.end()) { cout << "找到 key=2,value = " << it->second << endl ; } else { cout << "未找到 key=2" << endl ; } cout << "key=1 的个数: " << m.count(1 ) << endl ; cout << "key=5 的个数: " << m.count(5 ) << endl ; m.erase(3 ); cout << "\n=== map 删除 key=3 后 ===" << endl ; for (auto & p : m) { cout << p.first << ": " << p.second << " " ; } cout << endl ; multimap <int , string > mm; mm.insert(pair <int , string >(1 , "Apple" )); mm.insert(pair <int , string >(1 , "Banana" )); mm.insert(pair <int , string >(2 , "Cat" )); cout << "\n=== multimap 插入结果(支持重复键) ===" << endl ; for (auto & p : mm) { cout << p.first << ": " << p.second << " " ; } cout << endl ; cout << "key=1 出现的次数: " << mm.count(1 ) << endl ; mm.erase(1 ); cout << "=== multimap 删除 key=1 后 ===" << endl ; for (auto & p : mm) { cout << p.first << ": " << p.second << " " ; } cout << endl ; return 0 ; }
=== map 插入结果(自动排序) === 1 : one 2 : two 3 : three 4 : four 找到 key=2 ,value = two key=1 的个数: 1 key=5 的个数: 0 === map 删除 key=3 后 === 1 : one 2 : two 4 : four === multimap 插入结果(支持重复键) === 1 : Apple 1 : Banana 2 : Cat key=1 出现的次数: 2 === multimap 删除 key=1 后 === 2 : Cat
map 和set 都是有序容器,所以内部会自动进行排序。自然也可以指定比较规则,来应对自定义的数据类型
下面这里有说,map<key, value>, key的类型要可比较 ,value无要求。
STL 函数对象 (仿函数) 函数对象 基本概念
重载 operator() 的类对象 ,行为类似函数 ,又称仿函数 。
作为算法的策略参数 (指定自定义变量的比较规则),比普通函数更灵活(可携带状态 )。
谓词 一元谓词
一元谓词 :接受一个参数 ,返回 bool ,用于筛选。
二元谓词
二元谓词 :接受两个参数 ,返回 bool ,用于比较 / 排序
内建函数对象 STL 提供了预定义仿函数 ,包含在 <functional> 头文件:
STL 常用算法