c++笔记

秋招笔记

开篇

知识点

  • 算法

    • 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;
  • ++运算符的优先级高于*

  • 空指针

    • 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;
  • 万能指针,一般用于函数中

    • 对于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’结束。

字符指针和字符串

  • 字符常量存放在常量区,如果使用指针,需要加上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; // &不是取地址运算符,而是表示引用
  • 一个变量可以有多个引用(别名)

  • 引用注意事项

    • 1.引用必须引用合法的内存空间
      int& rnum2 = 10; // 错误。
      2.引用必须初始化
      int& rnum2;
      3.引用一旦初始化,不能改变引用

    • 引用的本质:通过指针常量取实现的。

      • int a = 10;
        int & aref = a; // int * const rf = &a;
        aref = 20; // *rf = 20;
  • 数组引用

    • // 4.数组的引用
      int arr[3] = { 1,2,3 };
      int(&rarr)[3] = arr;
      rarr[1] = 100;
      cout << arr[1] << endl;
  • 左值与右值

    • 左值:是可以被引用的数据对象。例如:变量、数组元素、结构体变量的成员、引用、解除引用的指针
      右值(非左值):字面常量、包含多项的表达式。
      普通变量属于可修改的左值,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”
        }

_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 p2;
      Person p3(30)
  • 隐示转换法

    • 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.静态的成员函数不能非静态的成员变量,但是可以访问静态的成员变量
      2.普通的成员函数可以访问静态或者非静态的成员变量、成员函数。
      3.可以通过类名或者对象名来访问。

单例设计模式

  • 一个类的对象只能存在一个

  • 1.私有化构造函数,防止别人随便创建构造函数

  • 2.私有化一个静态实例,被该类的所有对象共享

  • 3.获取实例

    • 这类中提供一个方法让别人获取实例

    • // 饿汉式
      static ChairMan* getInstance() {
      return singleMan;
      }

      • 一开始就创建出来
    • // 称为懒汉式
      static ChairMan* getInstance() {
      if (singleMan == nullptr) {
      singleMan = new ChairMan;
      }
      return singleMan;
      }

      • 一开始不创建,等使用时候在创建

this指针

  • 保存自己这个对象的地址

    •   (*this).name = name;
      
      this->age = age;
    • 返回该对象本身 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;
      }// 身高
  • const 加上函数的后面

常对象

  • const Person p2(30); // 常对象

    • // p2.m_Age = 20; // 常对象不能修改内容
      p2.m_Height = 190; // 常对象可以修改mutable修饰的成员变量
    • p2.show(); // 常对象可以调用常函数
      // p2.show1(); // 常对象不能调用普通函数

友元

  • 友元(全局)函数

    • 将函数的声明放到类中,并加上关键字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);

继承

  • 继承方式

    • 访问控制一共有3个:
      1.public: 公共的访问控制,被修饰的成员在类和类外都能够被访问
      2.private: 私有的访问控制,被修饰的成员只能在本类中访问,在类外不能够被访问
      3.protected: 受保护的访问控制,如果【没有继承关系,和private的特点一样】。

    在继承关系中,父类中protected修饰的成员,【子类中可以直接访问,但是在类外的其他地方不能访问】。

    成员变量一般使用私有访问控制,不要使用受保护的访问控制。
    成员方法如果想要让子类访问,但是不想让外界访问,可以使用受保护的访问控制。

  • 对象模型

    • 派生类, 如果继承的时候没有写继承方式,那么默认使用私有继承

    • 类中的成员方法和成员变量是分开存储的

    • 子类把父类的私有变量也继承下来了,但是却无法访问

  • 继承中的构造和析构

    • 先调用父类的构造,(如果有:调用数据成员的构造函数),在调用子类的构造

    • 先析构子类的析构,(如果有:调用数据成员的析构函数),,在调用子类的析构

    • 指定选择父类的构造函数

      • Son1() : Base1(10){
        cout << “Son1的构造函数” << endl;
        }

    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>;
  • 空指针

    • 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 过期时,会释放一块内
      存两次
      1. 重载赋值运算符,使之执行深复制。这样两个指针指向不同的对象,其中的一个对象是另一个对象的副本;
  1. 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象。然后,让赋值操作转让所有权。这就是用于 auto_ptr和 unique_ptr 的策略,但 unique_ptr 的策略更严格。
  2. 创建智能更高的指针,跟踪引用特定对象的智能指针计数。这称为引用计数(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;

显示转换运算符

  • 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 – 索引
  • 使用静态库

    • 把库文件发给别人,把头文件发给别人,因为只有知道头文件才知道你的库里有哪些功能

动态库

  • 动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。

  • 动态库是可执行文件

  • 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() 的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大
        量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害,应当避免
    • 进程回收

      • 父进程可以通过调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程
    • 进程退出

      • #include <stdlib.h>
        void exit(int status);
        #include <unistd.h>
        void _exit(int status);
  • 进程间通信方式

    • 通信方式

      • 单工:电视

      • 半双工:对讲机

      • 全双工:手机电话

    • 匿名管道/管道

      • 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值