秋招笔记
开篇
知识点
-
算法
- 200道以上
-
操作系统
-
计算机网络
-
C/C++语言
-
Linux C/C++开发
-
数据库
-
项目经验
指针
内存地址
-
32位的系统支持2的32次方的内存:4G;64位的系统支持2的64次方的内存
-
&取地址
-
所有地址所占的空间是一样的
- 32位的系统分配32位编码,4个字节
- 64位的系统分配64位编码,8个字节
指针和指针变量
-
是一种独立的数据类型,这种类型的变量存储的值是内存地址,指针不是地址切记
-
指针变量存放内存的地址,不是普通的数据,因为地址类型都一样,所以指针变量所占的存储单元的长度都一样。
- sizeof(指针)=定值
-
局部变量保存在栈上
-
注意:指针变量存放地址,要注意指针本身的地址和指针指向的地址
- &num和p是同一个值,都是地址
指针声明注意
- 对每个指针变量名,都需要使用一个*
int* p1, p2; (声明创建一个指针变量p1和一个int类型变量p2)
int *p1, *p2; (声明创建一个指针变量p1和一个指针变量p2)
使用指针
-
解除引用 *
-
应用于指针变量,可以得到该地址处存储的值
-
使用 *指针前,一定要初始化指针,不然会造成(野指针)。
-
不能简单的将整数赋值给指针变量
- int * ptr = 0xB80000000;//报错int类型赋值给int *
int * ptr = (int *) 0xB8000000;
- int * ptr = 0xB80000000;//报错int类型赋值给int *
-
-
++运算符的优先级高于*
-
空指针
-
0
-
NULL
-
nullptr
- 指针类型,只能用指针接收
-
指针的宽度和跨度
-
int * (宽度取4个字节的数据),short * (取2个字节的数据)
-
指针变量的自身类型:去掉变量名,剩余的部分就是指针变量的自身类型。
- int ** ptr; 指针变量 ptr 的自身类型是 int **
-
指针变量的指向类型:去掉变量名及离它最近的一个*,剩余的部分就是指针变量指向的类型。
- int ** ptr; 指针变量 ptr 指向的类型是 int *
-
大端和小端
-
int num = 0x01020304;
int *p = #
cout << *p << endl;
short *p1 = (short *)#
cout << *p1 << endl;- 0x01020304
- 0x0304
-
-
指针+1
- 跳一个类型长度的跨度
-
*和&
- 对变量取地址 & , 整个表达式类型上加上一个 *
- 在使用中,对指针变量使用 * ,整个表达式类型上减少一个 *
- 注意:在使用中,当和&同时出现时,从右往左,依次抵消。&&&num == &num &&*&*p == p
void指针
-
无类型指针
-
void * 指针变量可以指向任意变量的内存空间,任何类型指针变量都可以转为 void *
- int num = 10;
void * p = #
int * p_num = #
void * p1 = p_num;
- int num = 10;
-
万能指针,一般用于函数中
- 对于void * 不要使用解除引用操作
- 如果必须使用解除引用,要进行强制类型转换
const指针
-
常量的指针
-
指针指向的数据是一个常量,常量的指针,常指针
-
const int * p1=#
-
const在的左边 修饰的是 (*p1只读的, p1可读可写)
- 数据不能修改 报错*p1=某值
-
const int* p1 = #
//*p1 = 20;
int data = 20;
p1 = &data;
cout << *p1 << endl;
-
-
指针常量
-
指针本身是一个常量,指针常量
-
int* const p2 = #
-
const在*的右边 修饰的是p2, (*p2可读可写 p2只读)
- 指针中的内容不可修改 报错 p2=&num2
-
int* const p2 = #
*p2 = 200;
cout << *p2 << endl;
// p2 = &data;
-
-
const int* const p3 = #
- const在的左右两边 既修饰 也修饰指针变量(*p3只读 p3只读)
- 指针的指向*p3不可修改,指针本身,指针中的内容也不可修改
- const int* const p3 = #
// *p3 = 10;
// p3 = &data;
指针和数组
-
一维数组
-
int arr[5]={1,2,3,4,5};
-
sizeof(arr);
- 20=元素个数*类型所占字节数
-
sizeof(rr[0]);
- 类型所占字节
-
数组名可以作为地址,代表的是首元素的地址
-
arr+1,地址的跨度加4
-
arr[1]等价于*(arr+1)
-
推导,数组名作为地址,是首元素的地址
&arr[0] == &*(arr + 0) == arr + 0 == arr -
数组元素的个数:sizeof(arr) / sizeof(arr[0]
-
// 不要认为p只能保存首元素的地址
int* p1 = &arr[2];
cout << "p1[1] = " << p1[1] << endl;//前面的元素
cout << "p1[-1] = " << p1[-1] << endl;//后面的元素
-
-
char str[] = { ‘h’,‘e’,’\0’ };//字符串,3
char str1[] = { ‘h’,‘e’ };//字符,2
char str2[] = “hello”;//字符串,6
-
-
指针数组
-
int *arr[4];//数组里面的元素保存地址
- arr[0]=&num0
- arr[1]=&num1
- arr[2]=&num2
-
-
数组指针
-
指针指向的是数组的
- // 创建一个数组
int arr[5] = { 10,20,30,40,50 };
// 数组指针
int (*p)[5] = &arr; - 此时要数组指针中存放的是数组的地址
- 一定要加上括号,+1跨度为数组的总大小
- // 创建一个数组
-
二级指针
-
int num = 10;
int * p = #
int ** q = &p;
*p : 获取的是 num 的值
*q : 获取的是 num 的地址值
**q : 获取的是 num 的值
指针和字符串
-
char flower[10] = “rose”;
cout << flower << “s are red” << endl;
const char* p_flower = “rose”;
cout << p_flower << endl;- // 数组名flower是首元素的地址,也就是r字符的地址。
// 当cout对象在处理字符地址的时候,不是打印地址,而是打印这个地址对应的字符的内容。
// 把它当成字符串处理,也就是继续往后输出后面的字符的内容,直到遇到空字符’\0’结束。
- // 数组名flower是首元素的地址,也就是r字符的地址。
字符指针和字符串
-
字符常量存放在常量区,如果使用指针,需要加上const,常量指针
- const char* str1 = “hello world”;
- 只读,不可修改
-
字符数组,char str2[] = “hello world”;
- 可读可改
-
// 区别
// 字符串指针变量
// str1 变量,在栈区开辟空间,“hello world"在文字常量区开辟空间
// str1 仅仅保存"hello world"首元素的地址,不是字符串常量本身。
// str1 不能对"hello world 内容进行修改(写的操作)”
// str1 本质是变量,可以++// 字符串(字符数组)
// str2根据初始化字符串的大小开辟空间,并且直接存储该字符串的内容
// 可以通过str2对"helloworld"进行写操作。
// str2是数组名,是一个符号常量,不能++
函数
防止头文件重复包含
- #ifndef _FUN_H
#define _FUN_H
int getSum(int, int);
int num = 10;
#endif
- #pragma once
- __pragma(“once”)
不要返回局部变量的地址
- int* getIntAddr1() {
int a = 10;
return &a;
} - 因为局部变量当函数调用完毕以后会自动释放内存,会导致野指针
动态开辟内存
-
char* buildstr(char c, int n) {
// 动态创建一个字符串
char* pstr = new char[n + 1];
pstr[n] = ‘\0’;
while (n-- > 0) {
pstr[n] = c;
}
return pstr;
}- new可以在堆(自由存储区)开辟内存
- char* pstr = new char[n + 1];
- int *p=new int;
-
delete[] ps;
- 堆中的数据和栈中的数据不一样,函数就是后,栈中的数据释放,堆中的数据要手动释放,不然不会释放
函数内修改外部指针的指向
- 函数的内部修改外部指针变量的指向。(函数的内部修改外部变量的值)
需要在函数内部修改外部指针变量的值,需要传递外部指针变量的地址值。
函数与数组
- void printArr(int arr[]) { // 优化 int * p
函数与字符串
-
strlen(字符串长度,去掉/0后的)
-
sizeof(空间大小)
-
char arr[15] = “helloworld”;
const char* str = “helloworld”;int n1 = strlen(arr);
int n2 = strlen(str);
int n3 = strlen(“helloworld”);cout << n1 << endl;//10
cout << n2 << endl;//10
cout << n3 << endl;//10
cout << sizeof(arr) << endl;//15 数组空间大小
cout << sizeof(str) << endl;//8 字符指针的内存大小(64位机)
cout << sizeof(“helloworld”) << endl;//11 字符常量大小 -
判断字符结束
- while (*str)
函数与结构体
- 结构体与类的区别
- c和c++中struct的区别
main参数
-
int main(int argc, char* argv[])
- argc:表示传递给main函数的参数的个数。
- argv:传递给main函数指针数组,用来接收所有的参数的
递归
- 1.递归一定要有出口。不然会产生死递归。Stack Overflow 栈内存溢出的错误。
2.递归的次数不能过多。
函数指针
-
函数的地址就是存储其机器语言代码的内存的开始地址
-
函数名代表入口地址
-
函数名后面加上(),叫调用函数
-
int (*pf)(int, int) = getSum;
- 一定要加上括号,不然可能与函数的声明歧义
宏函数
-
#define 名字 需要替换的量
-
宏缺陷
- #define GETSUM(x, y) x+y // 宏函数cout << GETSUM(a, b) * 5 << endl;
- 三目运算
-
以空间换时间,适用于短小的函数
内联函数
-
在普通函数前面加上关键字inline
-
替换而非调用,解决性能问题
-
不做内敛编译
-
- 1.不能存在任何形式的循环语句
-
- 2.不能存在过多的条件判断语句
- 3.函数体不能过于庞大
- 4.不能对函数进行取地址的操作
引用
-
起别名
- int num = 10;
// 给num定义一个引用(别名)
int& rnum = num; // &不是取地址运算符,而是表示引用
- int num = 10;
-
一个变量可以有多个引用(别名)
-
引用注意事项
-
1.引用必须引用合法的内存空间
int& rnum2 = 10; // 错误。
2.引用必须初始化
int& rnum2;
3.引用一旦初始化,不能改变引用 -
引用的本质:通过指针常量取实现的。
- int a = 10;
int & aref = a; // int * const rf = &a;
aref = 20; // *rf = 20;
- int a = 10;
-
-
数组引用
- // 4.数组的引用
int arr[3] = { 1,2,3 };
int(&rarr)[3] = arr;
rarr[1] = 100;
cout << arr[1] << endl;
- // 4.数组的引用
-
左值与右值
-
左值:是可以被引用的数据对象。例如:变量、数组元素、结构体变量的成员、引用、解除引用的指针
右值(非左值):字面常量、包含多项的表达式。
普通变量属于可修改的左值,const变量属于不可修改的左值。 -
常量引用
const int& b = 10;
- 内部操作:const int temp = 10; const int& b = temp;
-
默认参数
- 如果某个参数有了默认值,那么从这个位置开始,后面的参数都必须有默认值。
默认参数只能从右往左添加默认值
函数重载
-
相同作用域下
- 参数个数不同
- 类型不同
- 排序顺序不用
-
函数的返回值不可用作为重载的条件
-
默认参数和函数重载一起使用。要注意二义性的问题。
-
函数重载的原理:由于编译器采用了一种技术,name decoration(名称修饰)
extern c
- extern “C” : 被extern "C"所修饰的代码会按照C语言的方式去编译
- extern “C” void fun() {
}
-
声明
-
extern “C” void fun() {}
extern “C” void fun(int v) {} -
extern “C” {
void fun() {}
void fun(int v) {}
} -
c语言方式解析整个文件
- extern “C” {
#include “math.h”
}
- extern “C” {
-
_cplusplus
-
#ifdef __cplusplus
extern “C” {
#endif
int add(int, int);
int sub(int, int);
int divide(int, int);
#ifdef __cplusplus
}
#endif- 当cpp调用时,宏使用extern
字符常用的API
面向对象
构造函数
-
构造函数功能:对对象成员进行初始化的
-
如果什么都不写,系统会系统默认构造,如果写了有参构造或者拷贝构造函数,系统则不会提供默认的构造函数,此时需要用户自己去写默认构造
-
拷贝构造
- 调用时机:一个对象初始化另一个对象时
- Person(const & Person p) { // const & Person p = p1;
}
- 一定要加上引用&,不然会陷入无限的递归
- 加上const,防止修改内容
-
显示调用
- Person p = Person(); // Person p;
Person p1 = Person(20);
- Person p = Person(); // Person p;
-
隐示调用
- Person p2;
Person p3(30)
- Person p2;
-
隐示转换法
- Person p=p1;//利用隐式转换法调用拷贝构造函数,转换 Person p = Person(p1);Person p=10;编译器会转换成 Person p = Person(10);
-
匿名对象
- 匿名对象, 本行执行完了以后立即释放
Person();
Person§; // 编译器认为 Person p;
- 匿名对象, 本行执行完了以后立即释放
析构函数
- 对象销毁的时候,会执行
- 释放资源
深拷贝和浅拷贝
- 默认构造时简单的值拷贝
- 深拷贝,新开辟空间,进行拷贝
初始化列表
- Person(string name, string pname, string gname) : m_Name(name), m_Phone(pname), m_Game(gname) {
cout << “Person的有参构造” << endl;
}
静态成员
-
关键字static进行声明
-
静态成员变量
- 1.必须在类中声明,在类外定义
2.静态成员变量不属于某个对象,而是属于类,所有对象共享
3.静态成员变量可以通过类名或者对象名来获取。
- 1.必须在类中声明,在类外定义
-
静态成员函数
- 1.静态的成员函数不能非静态的成员变量,但是可以访问静态的成员变量
2.普通的成员函数可以访问静态或者非静态的成员变量、成员函数。
3.可以通过类名或者对象名来访问。
- 1.静态的成员函数不能非静态的成员变量,但是可以访问静态的成员变量
单例设计模式
-
一个类的对象只能存在一个
-
1.私有化构造函数,防止别人随便创建构造函数
-
2.私有化一个静态实例,被该类的所有对象共享
-
3.获取实例
-
这类中提供一个方法让别人获取实例
-
// 饿汉式
static ChairMan* getInstance() {
return singleMan;
}- 一开始就创建出来
-
// 称为懒汉式
static ChairMan* getInstance() {
if (singleMan == nullptr) {
singleMan = new ChairMan;
}
return singleMan;
}- 一开始不创建,等使用时候在创建
-
this指针
-
保存自己这个对象的地址
-
this->age = age;(*this).name = name;
- 返回该对象本身 return *this;
-
常函数
-
用const修饰成员函数时,const修饰this指针指向的内存区域
-
成员函数体内不能修改本类中的任何普通成员变量
-
如果必须要修改,在变量前面加上:mutable
- int m_Age; // 年龄
mutable int m_Height;
void show() const { // 常函数
// this = nullptr;
// this->m_Age = 200;
this->m_Height = 180;
cout << this->m_Age << endl;
}// 身高
- int m_Age; // 年龄
-
const 加上函数的后面
常对象
-
const Person p2(30); // 常对象
- // p2.m_Age = 20; // 常对象不能修改内容
p2.m_Height = 190; // 常对象可以修改mutable修饰的成员变量 - p2.show(); // 常对象可以调用常函数
// p2.show1(); // 常对象不能调用普通函数
- // p2.m_Age = 20; // 常对象不能修改内容
友元
-
友元(全局)函数
- 将函数的声明放到类中,并加上关键字friend
-
友元类
- 将(别的类)声明放到类中,并加上关键字friend
-
友元成员函数
-
将成员函数的声明放到类中,并加上关键字friend
- friend void GoodFriend::visit(Building& building);
- 别忘记加上作用域了
-
运算符重载
-
operator符号
-
Time t3 = t1 + t2;
- 类内:Time t3=t1.operator+(t2)
- 类外:Time t3=operator+(t1,t2)
-
-
加号运算符
-
左移运算符
-
递增运算符
-
指针运算符
-
智能指针
- // 智能指针类
class SmartPointer {
public:
- // 智能指针类
Person* operator->() {
return this->m_Person;
}Person& operator*() {
return *m_Person;
}SmartPointer(Person* p) {
m_Person = p;
}Person* m_Person; // 内部维护的Person的指针
~SmartPointer() {
cout << “SmartPointer析构函数” << endl;
if (this->m_Person != nullptr) {
delete this->m_Person;
this->m_Person = nullptr;
}
}
};
- int main() {// 创建Person的对象
Person* p = new Person(30);
p->showAge();
(*p).showAge();// delete p;
cout << “==============” << endl;SmartPointer sp§;
sp->showAge(); // sp.operator->();
// sp->->showAge(); 编译器会优化成 sp->showAge();
(*sp).showAge();return 0;
} -
-
赋值运算符
-
Person& operator=(const Person& p) {
// 先判断自身堆区是否有数据,如果有先释放
if (m_Name != nullptr) {
delete[] m_Name;
m_Name = nullptr;
}m_Name = new char[strlen(p.m_Name) + 1];
strcpy(m_Name, p.m_Name);return *this;
} -
默认情况下会生成一个默认的=运算符,单纯的值拷贝,浅拷贝
-
如果需要深拷贝,需要重写,重载=运算符
-
-
中括号运算符
-
关系运算符
-
函数调用运算符
- 仿函数,本身是对象
类的类型转换
-
explicit修饰的构造函数不能用于自动类型转换(隐式类型转换)
- explicit MyString(int len) {
cout << “MyString有参构造MyString(int len)…” << endl;
} - 报错:MyString str1 = 10;//不能隐式转换
- 正确:MyString str1 = MyString(10);
- explicit MyString(int len) {
继承
-
继承方式
- 访问控制一共有3个:
1.public: 公共的访问控制,被修饰的成员在类和类外都能够被访问
2.private: 私有的访问控制,被修饰的成员只能在本类中访问,在类外不能够被访问
3.protected: 受保护的访问控制,如果【没有继承关系,和private的特点一样】。
在继承关系中,父类中protected修饰的成员,【子类中可以直接访问,但是在类外的其他地方不能访问】。
成员变量一般使用私有访问控制,不要使用受保护的访问控制。
成员方法如果想要让子类访问,但是不想让外界访问,可以使用受保护的访问控制。 - 访问控制一共有3个:
-
对象模型
-
派生类, 如果继承的时候没有写继承方式,那么默认使用私有继承
-
类中的成员方法和成员变量是分开存储的
-
子类把父类的私有变量也继承下来了,但是却无法访问
-
-
继承中的构造和析构
-
先调用父类的构造,(如果有:调用数据成员的构造函数),在调用子类的构造
-
先析构子类的析构,(如果有:调用数据成员的析构函数),,在调用子类的析构
-
指定选择父类的构造函数
- Son1() : Base1(10){
cout << “Son1的构造函数” << endl;
}
- Son1() : Base1(10){
Son1(int b) : Base1(b) {
this->m_B = b;
cout << “Son1的构造函数” << endl;
} -
-
继承中的同名成员处理
-
如果子类和父类出现同名的成员,那么通过子类对象去访问同名的成员,访问的是子类中的。
-
通过子类对象访问父类中的同名的非静态成员,需要加上作用域
- 父类:cout << s.Base::m_A << endl;
-
// 如果子类中出现了和父类同名的成员函数,子类的成员函数会隐藏掉父类中所有同名的成员函数。
// 子类重定义父类的成员函数,如果想要调用父类中同名的成员函数,必须加上作用域 -
如果是静态的成员
- 要加上作用域
- // 通过类名去访问静态的成员
// 静态成员变量
cout << Son::m_A << endl;
cout << Base::m_A << endl;
cout << Son::Base::m_A << endl;
-
-
类的对象模型
- cl /d1 reportSingleClassLayout类名 文件名
-
多继承
- class Son :public Base1, public Base2 {}
- 二义性:可能会导致二义性,需要加上作用域
- cout << "Base2::m_A : " << s.Base2::m_A << endl;
cout << "Base2::m_B : " << s.Base2::m_B << endl;
-
菱形继承
- 二义性,作用域
- 继承了多份数据,浪费内存
- 解决方法:虚继承,加上virtual后就是虚继承,Person类被称为虚基类。
-
虚继承的对象模型
- cout << "Singer的偏移量 : " << *((int )((long long *)&sw)+1)<< endl;
多态
-
静态联编
-
地址早绑定
-
编译期间就知道了speak函数的地址
- 如函数重载,运算符重载
-
-
动态联编
-
地址晚绑定
-
执行期才知道函数的地址
-
父类的指针或者引用指向子类的数据
-
virtual ,存放虚函数表(vfptr),4个字节(x86)
- 空类的sizeof的字节为:1,维护用的
-
-
企业开发原则
- 开闭原则。对扩展进行开放,对修改进行关闭
-
纯虚函数->抽象类
-
纯虚函数的语法: virtual 返回值类型 函数名(形参列表) = 0;
纯虚函数,不需要有实现,可以和普通的函数共存
有了纯虚函数的类,也称为抽象类
抽象类无法实例化对象 -
virtual void func() = 0;
- 子类必须要重写父类纯虚函数,否则子类也是抽象类
-
-
纯虚析构
-
利用虚析构可以解决 不调用子类的析构函数的问题
- 基类普通虚构,子类分布
- 基类纯虚函数,子类分布
-
需要有声明,也需要有实现
-
纯虚析构需要在类外进行实现
-
如果类中有了纯虚析构,那么这个类就是抽象类
-
模板
泛型编程
- 模板:减少重复的代码,提高可复用性
函数模板
-
template // T属于通用的数据类型,告诉编译器下面出现这个T类型,不要报错
-
typename
-
class
-
自动类型推导或者指定类型,编译的时候生成具体的函数
-
区别
- 函数模板不允许自动类型转化
- 普通函数能够自动进行类型转化
- 例如’c’转换成对于的ASCII
调用规则
- 如果普通函数和函数模板都可以匹配,优先使用普通函数
- 函数模板也可以发生函数重载
- 如果函数模板可以产生更好的匹配,那么优先使用函数模板
类模板
- template<class NAMETYPE, class AGETYPE = int>
- 类模板中的成员函数并不是一开始创建,而是在使用的时候才生成,在替换T后生成
类模板分文件编写
- 把类的声明和显示放到同一个文件中,文件名后缀改为.hpp
- 编译的时候不会生成模板,只要在使用的时候才会创建模板
STL
容器、算法、迭代器
迭代器
-
输入/输出
-
前向
-
双向
-
随机访问迭代器
- begin()指向第一个元素,end()指向最后一个元素的末尾
string
- 构造
- 赋值
- 拼接
vector
-
遍历
- for (vector::iterator it = v.begin(); it != v.end(); it++)
- vector::iterator itBegin = v.begin();
// v.end() 结束迭代器 指向容器中最后一个元素的下一个位置
vector::iterator itEnd = v.end();
while (itBegin != itEnd)
- for_each(v.begin(), v.end(), myPrint);
stack
- #include std:stack s
- s.top()
- s.empty()
- s.push(x)
- s.pop()
- s.size()
queue
- #include queue q
- q.empty
- q.front
- q.back
- q.pop
- q.push(x)
- q.size()
priority_queue
- 最大堆,最小堆,最大最小的完全二叉树
- priority_queue 默认构造最大堆
- priority_queue<int ,vector,greater> h最小堆
- priority_queue<int ,vector,less> h最大堆
- h.empty()
- h.pop()
- h.push(x)
- h.top()
- h.size()
map
C++11新特性
新类型
-
新增了类型 long long 和 unsigned long long,以支持 64 位(或更宽)的整型
-
新增了类型
char16_t 和 char32_t,以支持 16 位和 32 位的字符表示 -
新增"原始"字符串
- R"(原始的字符串)"
- R"+(C:\Program Files (x86)"\Application Verifier)+
统一的初始化
-
C++ 11 扩大了用大括号括起的列表(初始化列表)的适用范围
- 可以加等号,也可以不加
- int x = { 5 };
double y{ 2.75 }; - short quar[5]{ 1, 2, 3, 4, 5 };
vector v{ 1,2,3,4,5 };
int* ar = new int[4]{ 2, 3, 4, 5 };
Stump s1(3, 15.6);
声明
-
auto
- 实现自动类型推断,要求进行显示初始化,让编译器能够将变量的类型设置为初始值的类型
- for (auto it = v.begin(); it != v.end(); it++) {
cout << *it << endl;
}
-
decltype
- decltype 将变量的类型声明为表达式指定的类型。
- decltype(expression) var;
- decltype(x) y; // 让y的类型与x相同,x是一个表达式
-
decltype规则
- 第一步:如果 expression 是一个没有用括号括起的标识符,则 var 的类型与该标识符的类型相同,包括 const 等限定符
- 第二步:如果 expression 是一个函数调用,则 var 的类型与函数的返回类型相同
- 第三步:如果 expression 是一个左值,则 var 为指向其类型的引用。要进入这一步,expression 不能是未用括号括起的标识符。
- 第四步:如果前面的条件都不满足,则 var 的类型与 expression 的类型相同
-
返回类型后置
- 在函数名和参数列表后面(而不是前面)指定返回类型
- auto f2(double, int) -> double;
// -> double 称为后置返回类型,auto是一个占位符,表示后置返回类型提供的类型 - template<typename T1, typename T2)
auto eff(T1 x, T2 y) -> decltype(x*y){
// decltype 在参数声明后面,因此x和y位于作用域内,可以使用
// …
return x * y;
}
-
模板别名
-
using =
-
对于冗长或复杂的标识符,如果能够创建其别名将很方便。之前,C++ 提供了 typedef
-
差别在于,这种方式也可以用于模板部分具体化,但 typedef 不能
- template
using arr12 = std::array<T,12>;
- template
-
-
-
空指针
- nullptr
智能指针
-
如果在程序中使用 new 从堆(自由存储区)分配内存,等到不再需要时,应使用 delete 将其释放
-
智能指针是行为类似于指针的类
对象。 -
C++ 11 摒弃了 auto_ptr,并新增了三种智能指针:unique_ptr、shared_ptr 和 weak_ptr
-
使用指针指针:auto_ptr、unique_ptr 和 shared_ptr 这三个智能指针模板都定义了类似指针的对象,可以将 new 获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将使用 delete 来释放内存
- 头文件#include
- #include
void demo1(){
double * pd = new double;
*pt = 25.5;
}
void demo2() {
auto_ptr ap(new double);
*ap = 20.5;
}
-
所有智能指针类都有一个 explicit 构造函数,该构造函数将指针作为参数。因此不能自动将指针转换为智能指针对象
-
注意事项
- 如果ps 和 vo 是常规指针,则两个指针指向同一个 string 对象。这样会 ps 和 vo 过期时,会释放一块内
存两次 -
- 重载赋值运算符,使之执行深复制。这样两个指针指向不同的对象,其中的一个对象是另一个对象的副本;
- 如果ps 和 vo 是常规指针,则两个指针指向同一个 string 对象。这样会 ps 和 vo 过期时,会释放一块内
- 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象。然后,让赋值操作转让所有权。这就是用于 auto_ptr和 unique_ptr 的策略,但 unique_ptr 的策略更严格。
- 创建智能更高的指针,跟踪引用特定对象的智能指针计数。这称为引用计数(reference
counting)。例如,赋值时,计数将加 1,而指针过期时,计数将减 1。仅当最后一个指针过期
时,才调用 delete。这是 shared_ptr 采用的策略。-
unique_ptr 比 auto_ptr
- unique_ptr是唯一的
- 程序试图将一个unique_ptr赋给另一个时,如果源 unique_ptr是个临时右值,编译器允许这样做,如果源unique_ptr将存在一段时间,编译器将禁止这样做。
- unique_ptr 相比 auto_ptr 还有一个优点,它有一个可用于数组的变体。模板 auto_ptr 使用 delete 而不是 delete[],因此只能与 new 一起使用,而不能与 new [] 一起使用。但 unique_ptr 有使用 new []和 delete [] 的版本
- unique_ptr<double[]> pda(new double(5));
-
-
选择智能指针
- 如果程序要使用多个指向同一个对象的指针,应该选择 shared_ptr;
- 如果程序不需要多个指向同一个对象的指针,则可以使用 unique_ptr;
- 如果使用 new [] 分配内存,应该选择 unique_ptr;
- 如果函数使用 new 分配内存,并返回指向该内存的指针,将其返回类型声明为 unique_ptr 是不错的选
择。
-
weak_ptr
- 循环引用问题
- 办法就是将两个类中的一个成员变量改为 weak_ptr 对象,因为 weak_ptr 不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏
异常规范方面的修改
-
异常规范,标准委员会认为,指出函数不会引发异常有一定的价值,为此添加了关键字
noexcept:- void test() noexcept;
-
不抛出异常
作用域内枚举
-
两个枚举定义中的枚举量可能发生冲突。枚举名的作用域为枚举定义所属的作用域,这意味着如果在同一个作用域内定义两个枚举,它们的枚举成员不能同名
- enum class egg {Small, Medium, Large, Jumbo};
enum class t_shirt {Small, Medium, Large, Xlarge}; - 也可以使用关键字 struct 代替 class,新枚举要求进行显示限定,以免发生名称冲突
- egg choice = egg::Large;
- enum class egg {Small, Medium, Large, Jumbo};
显示转换运算符
-
explicit
- 禁止隐示转换
类内成员初始化
- class Person {
string name = “zs”;
int age {22}; // int age = 22;
public:
Person(){}
Person(string s, int a) : name(s), age(a) {};
}
右值引用
-
C++ 11 新增了右值引用(rvalue reference),这种引用可指向右值(即可出现在赋值表达式右边的值),但不能对其应用地址运算符。右值包括字面常量(C-风格字符串除外,它表示地址)、诸如 x + y等表达式以及返回值的函数(条件是该函数返回的不是引用),右值引用使用 && 声明
-
int && r1=13
- int temp=13;int &r1=temp;
移动语义
- 不将20000000 个字符复制到新地方,再删除原来的字符,而将字符留在原来的地方,并将 vstr_copy2 与之相关联。这类似于在计算机中移动文件的情形:实际文件还留在原来的地方,而只修改记录
- 要实现移动语义,需要采取某种方式,让编译器知道什么时候需要复制,什么时候不需要
- //添加移动构造函数
demo(demo&& d) :num(d.num) {
d.num = nullptr;
cout << “move construct!” << endl;
}
Lambda
-
- 值传递
-
&{}
- 引用传递
静态类型转换
- 可在转换基本的数据类型
- 父类转换成子类不安全的,但是可以转换
- 子类转换父类,安全,可能会有精度的损失
动态类型转换
- 不可以转换基本的数据类型
- 在没有发生多态的前提条件下,父类不能转换成子类
常量转换
- 用于指针和引用
重新解释转换
- 安全系数最低的
Linux\UNIX
GCC
-
编译,汇编,运行
-
gcc test.c
-
生成 a.out
-
预处理,包含头文件,去掉注释
- gcc -E test.c -o test.i
- -E 预编译
- -o 生成目标
-
编译
- gcc -S test.i -o test.S
- -S编译
-
汇编
- gcc -c test.S -o test.O
- -c汇编
-
链接
-
gcc test.O -o test.out
- test.out是可执行文件
-
-
-
宏
-
-D
- gcc test1.c -o test1.out -D (DEBUG自己定义的宏)
-
makefile
-
Makefile 规则:一个 Makefile 文件中可以有一个或者多个规则
-
makefile中的内容就是编译的过程
- 实现自动化编译
-
app:sub.c add.c mult.c div.c main.c
gcc sub.c add.c mult.c div.c main.c -o app -
(命令前必须 Tab 缩进)
-
自动变量
-
预定义变量
-
AR : 归档维护程序的名称,默认值为 ar
CC : C 编译器的名称,默认值为 cc
CXX : C++ 编译器的名称,默认值为 g++ -
$@ : 目标的完整名称
$< : 第一个依赖文件的名称
$^ : 所有的依赖文件- 只能用在命令中
- src=sub.o add.o mult.o div.o main.o
target=app
( t a r g e t ) : (target): (target):(src)
$(CC) $(src) -o $(target)
-
-
模式匹配
-
函数
- make clean,指向伪目标
- 所有的规则,都是为第一条规则服务的,切记
-
目标:最终要生成的文件(伪目标除外)
依赖:生成目标所需要的文件或是目标
命令:通过执行命令对依赖操作生成目标(命令前必须 Tab 缩进)
Makefile 中的其它规则一般都是为第一条规则服务的(重点)。 -
make会自动查找makefile文件
GDB(非重点)
-
gcc -g -Wall program.c -o program -g
-
-g表示带有调试信息的,可执行程序
-
调试
- gdb 可执行文件
- ctrl+l 清屏
-
查看任意文件代码
- l bubble.cpp:5
- l bubble.cpp:bubbleSort
-
打断点
- b 行号
- b 文件名:行号
-
运行程序
-
run
-
start
- 最开始停下来
-
-
-Wall 在尽量不影响程序行为的情况下选项打开所有 warning,也可以发现许多问题,避免
一些不必要的 BUG。 -
可能会问(如何多进程调试)
静态库
-
库文件是计算机上的一类文件,可以简单的把库文件看成一种代码仓库,库的好处:1.代码保密 2.方便部署和分发
-
静态库在程序的链接阶段被复制到了程序中
-
规则
-
静态库的制作
-
1.gcc获得.o文件
-
2.将 .o 文件打包,使用 ar 工具(archive)
-
ar rcs libxxx.a xxx.o xxx.o
- r – 将文件插入备存文件中
c – 建立备存文件
s – 索引
- r – 将文件插入备存文件中
-
-
使用静态库
- 把库文件发给别人,把头文件发给别人,因为只有知道头文件才知道你的库里有哪些功能
动态库
-
动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
-
动态库是可执行文件
-
gcc -c *.c
-
ar rcs libcalc.a *.o
-
使用库文件
- 把库和头文件都给别人
- gcc src/main.c -I ./include -L ./lib/ -l calc
- -I 头文件的路径,-L 库文件的路径 -l 库名(去掉lib后的名字)
-
使用
-
1.与静态库相似,把头文件和动态库打包给别人,后面的操作和今天库一模一样
-
2.配置环境变量
-
用户级别的配置
-
进入家目录 cd ~
-
执行 ll,找到.bashrc,编辑该文件
-
. .bashrc更新
-
系统级别的环境变量的配置
- vim /etc/profile,其他的类似
-
-
-
静态库与动态库的区别
- 静态库链接的时候,将程序打包到应用程序中
- 共享数据
文件IO
-
IO缓存区,提高效率
-
帮助文档
- man手册
-
文件相关函数
进程
并行
- 同时执行多个程序
并发
- 每一时刻只能有一个程序在执行
进程控制块PCB
- 进程相关信息的一个结构体
进程状态
- 运行态
- 就绪态
- 阻塞态
进程相关的指令
-
查看进程
- 状态
-
实时显示进程动态
- top
-
杀死进程
- kill命令并不是杀死进程,而是给进程发送一个信号
-
进程相关的函数
-
pid_t getpid(void);
pid_t getppid(void);
pid_t getpgid(pid_t pid);- 获取进程id
- 获取进程父id
- 获取进程主id
-
-
创建进程
-
返回值:
成功:子进程中返回 0,父进程中返回子进程 ID
失败:返回 -1- 失败的两个主要原因:
当前系统的进程数已经达到了系统规定的上限,这时 errno 的值被设置为 EAGAIN系统内存不足,这时 errno 的值被设置为 ENOMEM
- 失败的两个主要原因:
-
读时共享,写时拷贝,只有在写的时候,才会开辟空间,拷贝数据
-
exec
- 根据指定的文件名找到可执行文件,并用它来取代调用进程的内容
- 一般在子进程中替换可执行的文件
-
-
进程控制
-
孤儿进程
- 父进程运行结束,但子进程还在运行(未运行结束),这样的子进程就称为孤儿进程
- 每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init ,而 init 进程会循环地 wait() 它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害
-
僵尸进程
- 每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法自己释放掉,需要父进程去释放。进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
僵尸进程不能被 kill -9 杀死,这样就会导致一个问题,如果父进程不调用 wait() 或 waitpid() 的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大
量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免
- 每个进程结束之后, 都会释放自己地址空间中的用户区数据,内核区的 PCB 没有办法自己释放掉,需要父进程去释放。进程终止时,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
-
进程回收
- 父进程可以通过调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程
-
进程退出
- #include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
- #include <stdlib.h>
-
-
进程间通信方式
-
通信方式
-
单工:电视
-
半双工:对讲机
-
全双工:手机电话
-
-
匿名管道/管道
- IPC 进程间通信
- | 管道符
-
有名管道
- 匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO),也叫命名管道、FIFO文件
-
-
内存映射
- 对文件进行映射
-
信号
- 未决信号集合,阻塞信号集合
-
共享内存
- 1.创建共享内存端,2关联,3.通信,4.释放,5.删除共享内存
线程
重点
-
线程状态
-
线程同步
-
锁
- 什么是死锁
- 死锁解决方式
-
生产者\消费者模式
线程是共享虚拟地址空间的
概念
- 进程是正在运行的程序的实例,它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元,
- 用一个程序来创建多个进程,进程是由内核定义的抽象实体,并为该实体分配用以执行程序的
各项系统资源。 - 从内核的角度看,进程由用户内存空间和一系列内核数据结构组成,其中用户内存空间包含了程序代码及代码所使用的变量,而内核数据结构则用于维护进程状态信息。记录在内核数据结构中的信息包括许多与进程相关的标识号(IDs)、虚拟内存表、打开文件的描述符表、信号传递及处理的有关信息、进程资源使用及限制、当前工作目录和大量的其他信息。
并行和并发
- 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。
- 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
计算机网络
网卡
- MAC地址唯一
- 12个16进制的数,共48位
- 00-16-EA-AE-3C-40
ip
- 网际协议地址
- 32位的二进制数,4个字节的整数
- 点分十进制:a.b.c.d
子网掩码
端口
- 如果把 IP 地址比作一间房子,端口就是出入这间房子的门
- 端口是通过端口号来标记的,端口号只有整数,
范围是从 0 到65535(2^16-1)。
网络模型
网络协议
- UDP
- TCP
- IP
- 以太网帧协议
- ARP
封装
- 上层协议使用下层协议提供的服务
- 应用程序
数据在发送到物理网络上之前,将沿着协议栈从上往下依次传递。每层协议都将在上层数据的基础上加上自己的头部信息(有时还包括尾部信息),以实现该层的功能,这个过程就称为封装。
网络通信过程
socket
- socket是一套通信的接口,Linux 和 Windows 都有,但是有一些细微的差别
- 应用层进程利用网络协议交换数据的机制
- 套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,
是应用程序与网络协议根进行交互的接口。
字节序
- 字节在内存中的顺序
- 网络字节序:大端
- 主机字节序:不一定的
TCP通信
- 端口0-1023被占用的,尽量选择大一点的
三次握手
滑动窗口
四次挥手
TCP状态
- netstat -app | grep 999 查看网络状态
I/O多路复用
- select
- poll
- epoll
Linux项目
指令
-
更新源
- sudo apt update
-
查看IP信息
- ifconfig
-
安装sshd服务
- sudo apt install openssh-server
-
查看进场
- ps -ef | grep ssh
- 管道过滤出 ssh
-
解压zip文件
- unzip 文件名
本地远程vscode开发
-
remote development
-
win10免密
- ssh-keygen -t rsa
重启mysql服务器
-
mysql service mysql restart
-
mysql -u root -p
-
关闭
- quit