1.CV限定符
const
用于声明常量
声明常量变量:
const int MAX_SIZE = 100;
常量函数参数
//防止函数修改n,通常与引用一起使用
void fun(const int& n){}
常量成员函数
//保证函数不会修改成员变量
void classname::show(xxx) const;//定义
void show(xxx) const;//声明
1.const int* ptr = &age;
ptr是一个const的(int*)类型,不可以用*ptr去修改age,但是可以直接修改age。
const变量地址可以赋给const指针,不可赋给常规指针2.int* const ptr = &age;
ptr是const *int类型的指针,即ptr是const,但是指向的内容不是const。
ptr只能指向age,但是可以使用*ptr改变age的值3.int const *ptr = &age;
volatile
不进行编译优化,会从内存中取数据,而不是寄存器
某些情况,编译器会自动优化,修改变量存取过程。修改后,当一些变量存在cpu寄存器中时,编译器会让cpu直接从寄存器读取,而不是内存中。如果有一个线程对内存中该变量进行了修改,则cpu从寄存器读取而不是内存,就出问题。
tips:mutable(可变的)
2.函数指针和指针函数
函数指针
函数地址:就是函数名
如何获取函数指针:
double pam(int)//函数
double (*pf)(int);//函数指针
使用:(*pf) 与 pam()相等
区分:
double (*pf)(int);//返回double的函数指针
double *pf(int);//返回double*的函数
指针函数
返回值时指针的函数
3.类
定义
class MyClass:public BaseClass{ //public继承private:int myVar;int* data = new int;public:void myFun()//构造函数,特征:无返回值;函数名=类名;可以定义多个不同参数(多态)MyClass(int value, int data){int myVar = value;*data = data}//拷贝构造函数,特征:无返回值;函数名=类名;参数为MyClass &other;MyClass(const MyClass& other){//深拷贝,对每个成员变量挨个拷贝myVar = other.var;*data = *(other.data);}//析构函数,特征:无返回值;函数名"~类名"~MyClass(){//释放资源delete data;}//运算符重载,特征:返回值MyClass&;类名"operate重载的符号";MyClass& operator=(const MyClass& other){if(this != &other){myVar = other.myVar;delete data;data = new int;*data = *(other.data)}return *this;}
};
实例化
MyClass obj;
MyClass* obj = new MyClass();//new方式
this和*this
this //对象地址
*this //对象本身
封装
3中封装方式的区别:
继承
3个继承方式的区别:
多态
允许同样的函数接口在不同的对象上表现出不同行为,依靠虚函数和动态绑定实现。
重写
重写父类虚函数。
虚函数
使用virture关键字。派生类可以覆盖虚函数。通过使用指向基类对象的指针或者引用调用虚函数时,程序将根据运行实际对象类型来确定调用函数。
编译器对虚函数使用动态联编
动态联编:
静态联编:
虚函数列表:存放虚函数地址,当虚函数被重写,放入新的地址。tips:构造函数不能为虚析构函数应该为虚友元不能为虚
纯虚函数
即接口,只声明不实现,让子类实现
virtual int fun()=0;//声明,特征多一个"=0"
重载
在同一个作用域内,可以定义多个同名不同参数列表的函数。
1.函数名相同
2.参数列表必须不同(参数类型、数量、顺序)
3.返回类型可以相同可以不同
参数列表
1.参数类别即函数特征标(不包含返回值,所以返回类型可以不同)
2.参数加了引用和不加,特征标相同
运算符重载
1.自定义数学运算
2.容器类和迭代器:使用运算符重载提供方便操作,如.运算
3.输入输出流:例如cout和cin就对"<<"进行重载以处理不同类型的输入
4.比较和排序:重载"<","=="等
5.实现迭代器功能:例如"++","--"
重载的限制
重载的限制:1.重载后的运算至少有一个操作数时用户定义的类型2.使用运算符时不得违反运算符原来的句法规则3.不能创建新运算符4.不能重载sizeof、"."、".*"、"::"、"?:"等运算符5.以下运算符只能通过成员函数重载:"="、"()"、"[]"、"->"
运算符重载和友元
两种运算符重载
1.成员函数Time Time::operator*(double m) const{//用于实现time*m,其实是调用time*(m)}2.非成员函数(使用友元,调用Time的成员变量)Time Time::operator*(double m, const Time& t){//实现m*time。调用m*(m, time),但是m不是成员函数,没有权限调用time,所以就需要友元}
析构函数
1.资源释放
2.清理操作
3.继承关系下的调用顺序:派生类的析构函数会自动调用基类的虚构函数,逐层清理父类资源
4.析构顺序:1.成员变量按照声明的逆序进行销毁2.派生类先于基类进行销毁
tips:1.只能有一个2.可以自动生成3.小心处理指针和资源释放
深拷贝与浅拷贝
拷贝构造函数
定义:注意事项:1.系统会自动生成默认拷贝构造函数和赋值运算符重载,但是当涉及动态内存分配需要自己写。
程序在什么时候执行的是浅拷贝、深拷贝、=赋值运算?
1.值传递参数、以值返回对象、使用初始化列表进行对象初始化时都会调用拷贝构造函数
2.当两个同类型对象使用"="时,调用赋值运算符重载函数
友元
1.友元在类中声明,但不是成员函数,不需要classname::
2.不是成员函数,但是可以访问成员
3.只在声明处加friend关键字,定义处不加
友元函数
在一个类内部声明并定义的独立函数,但不属于该类的成员。通过friend关键字声明。可以直接访问该类的私有和保护成员。
使用场景:1.两个类需要共享私有数据,将一个类的成员函数声明为另一个类的友元函数2.重载二元运算符,通常将运算符重载函数声明为友元
友元类
友元类指在一个类中声明另一个类为其友元
使用场景:1.类共享数据2.代理模式、迭代器模式等中会使用到
友元成员函数
列表初始化
比普通初始化更严格,不允许缩窄
其他
struct和class区别:1.struct默认共有和公有继承,不自动生成构造函数2.class默认私有和私有继承,自动生成构造函数
4.内存泄漏
1.谨慎使用new和delete
2.使用智能指针
3.定期检查和测试
4.使用容器类和标准库
5.遵从编码规范
6.使用内存分析工具
5.智能指针
share_ptr:共享指针,使用引用计数器追踪有多少个指针指向同一个资源,只有当最后一个指针销毁时,才会释放资源。存在循环引用问题。
unique_ptr:独占指针确保只有一个指针可以访问该资源,会自动释放
weak_ptr:弱指针,没有计数器,解决循环引用问题。
6.堆和栈
1.分配方式:
2.空间大小限制
3.分配速度:
4.生命周期:栈自动;堆手动,处理不好会内存泄漏
5.数据访问方式:栈访问更快,内存地址是连续的;堆由内存管理分配,可能是不连续的
6.使用场景:栈一般是自动存储,保持局部变量、函数调用过程中的参数传递;堆动态(手动)创建,需要手动释放tips:自动存储静态存储动态存储
7.static
三种静态变量的作用域
int global = 1000; //所有文件,但需要extern
static int one = 1; //本文件内void fun(){static int two = 2;
} //
静态成员变量
1.所有类公用一个静态变量
2.静态成员变量需要在类外单独初始化
静态成员函数
1.只与类关联,不需要实例化就可调用,并可以直接访问静态成员函数
8.new和delete
需要手动释放,不要多次delete
在函数调用中的分配与释放需要注意的情况:
分配与释放
//分配
int* ptr = new int;
double* arr = new double[10];//释放
delete ptr;
delete[] arr;
ptr = nullptr; //置空防止野指针tips:C++中的arr:长度固定vector:长度可扩展
new和malloc
1.成功时new返回具体指针,malloc返回*void
2.失败时new返回异常,malloc返回null
3.是否需要指定内存大小
4.new/delete可重载,malloc/free是函数
5.new/delete会调用构造析构函数
6.delete时,new自动将指针指向nullptr;malloc没有
7.new和delete是运算符
9.引用与指针
引用定义
1.引用 = 别名
2.必须初始化,不能为空,一经初始化后不可改变
3.不支持地址运算
4.又叫左值引用(还有一个右值引用,用于移动语义)
将引用参数声明为const
1.避免无意中修改数据
2.使函数可以处理const和非const实参,否则只能接受非const
3.使函数能够正确生成并使用临时变量
当返回值为引用时
1.避免返回函数内定义的临时变量
2.应该返回参数传进来的引用,并且形参使const时,返回值也应该是const
3.或者返回该函数new出来的,但是必须记得在函数外delete,否则内存泄漏
10.模板
C++特性之一:泛型编程
定义
//模板类
template <typename T>
class Container{
private:T data;public:Container(T value):data(value){} //新特性,列表初始化void print(){std::cout << "Data:" << data << std::endl;}
};//模板函数
template <typename T>
T maximum(T a, T b){return (a > b ) ? a : b;
}
11.异常处理
1.
2.
12.STL常用容器
1.vector
2.list
3.deque
4.map
5.set
6.unorder_map
7.unorder_settips:stack和queue是容器适配器,可指明由其他容器作为底层实现
13.迭代器
用于遍历容器的抽象概念,可以理解为指针。
声明
std::vector<int>::iterator it;
其他迭代器
begin() //第一个元素
end() //超尾,指向最后一个元素的下一个
使用auto it 简化操作
14.命名空间
1.避免命名冲突
2.组织代码
3.全局声明隔离
使用
1.using namespace std放在函数定义之前,让文件中所有函数都能用
2.using namespace std放在函数内
3.在函数内使用using std::cout
4.直接使用std::cout
15.预处理器
在编译前对代码进行一系列文本替换和指令处理,通常以"#"开通,不是正真的C++代码。
文件包含
#include <iostream>
#include "../../xxx"C++与C的include的区别:1.新式C++没有扩展名2.C中#include<math.h>C++中#inclde<cmath>
宏定义
#define MAX(a, b) ((a) > (b)> ? (a) :(b))为什么要加这么多括号?因为宏定义只是进行简单的字符替换,非常容易出问题宏定义函数和内联函数的区别:
1.
2.
条件编译
1.#ifdef
2.#ifndef
3.#if
16.文件操作
文件写入
文件读取
追加写入
17.指针与数组
int arr[] = {1, 2, 3, 4};
int* ptr = arr;1.sizeof(arr)是数组长度;sizeof(ptr)是地址长度
2.arr是常量,ptr可修改
3.arr是第一个元素地址
4.++运算每次加一个类型的长度,即指向下一个元素地址
5.当arr作为参数传入函数时,sizeof(arr)是地址长度即4,所以当传数组名arr时一般还会传个数组长度n
18.排序算法
1.冒泡
2.选择
3.插入
4.快排
5.归并
6.堆排
19.设计模式
单例模式
观察者模式
工厂模式
适配器模式
策略模式
装饰器模式
模板方式模式
20.线程
创建
#include<thread>std::thread myThrad(theadFun); //函数名作为参数创建线程
myThread.join() //等待线程执行完毕
21.线程同步
互斥锁
22.Lambda表达式
匿名函数
定义
[capture list] (parameters) -> return_type{//函数体
}capture list:捕获列表,用于指定lamda中所使用的外部变量
22.内联函数
将内联函数插入调用点提高程序运行效率,减少上下文切换的开销。
1.声明和定义前加inline
2.内联不能递归
3.虚拟成员函数无法内联
内联与宏定义区别
1.内联函数是在编译过程中,编译器会进行检查,如果过于复杂会放弃内联,当做普通函数执行
2.宏定义是编译前的预处理,只进行简单字符替换,不进行检查
23.动态绑定
即运行时多态,在程序运行时根据实际对象类型决定调用哪个方法或函数。
24.移动语义
25.类型转换
隐式类型转换
int num = 3.14;
explicit关键字
explicit关键字用于关闭隐式转换,只允许显示类型转换,防止不必要的问题
c++98中explicit关键字不能用于类型转换
c++11支持
显示转换
C风格强转
int num = (int)3.14;tips:int强转是四舍五入,不是去掉小数部分
函数风格强转
int num = int(3.14);
dynamic_cast转换
//执行类层次间的安全向下转型或基类到派生类的向上转型
dynamic_cast<type>(expression); //运行时进行类型检查
自定义类型转换函数
类中可以定义转换函数
1.转换函数必须是类的方法
2.不能指定返回类型
3.不能有参数class MyType{
private:int value;
public:MyType(int v = 0) : value(v){}operator int() const{return value;}
};int main(){MyType obj(42);int num = obj; //隐式调用int num1 = static_cast<int>(obj); //显示调用return 0;
}
26.尾递归
27.浮点运算精度问题
阈值法
double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 1e-6;if(std::abs(a - b) < epsilon){//在可接受范围内
}
比较法
double a = 0.1 + 0.2;
double b = 0.3;
double relative_error = std::abs((a - b) / b);if(relative_error < 0.01){//误差在可接受范围内
}
28.数组
1.只有在定义时才能初始化int cards[4] = {1, 2, 3, 4};
2.如果初始化给的数量小于长度,其他默认为0int cards[10] = {0};
3.不指定长度int cards[] = {1, 2, 3, 4};C++特性
1.可以省略"="int cards[4] {1, 2, 3, 4};
2.可以{}无任何值表示默认为0int cards[10] = {};
3.禁止缩窄操作
29.字符串
1.sizeof():字节长度
2.strlen():可见字符长度
C风格
1."\0"结尾char dog[3] = {'d', 'o', 'g'}; //可修改,长度为4,默认\0char dog[] = "dog"; //常量,不可修改,长度为4
string
29.内存管理方式
1.自动存储:自动变量存于栈
2.静态存储:定义函数外的;static的;整个程序执行期间都存在
3.动态存储:
30.野指针和悬浮引用
野指针:指向已经释放或内存无效的指针
1.delete后未置空
2.返回局部变量的指针
3.函数参数指针被释放悬浮引用:指向已被销毁对象的引用
31.内存对齐
数据在内存中存储起始地址是某个值的倍数
提高cpu访问效率,减少内存访问次数tips:1.结构体和共同体的内存对齐,共用体长度是最大类型长度
32.char
1.默认情况下即不是无符号也不是有符号
2.可以显示指定有无符号unsigned char c;signed char c;