c++
c++
2023/6/1
➡️

auto 及 初始化

//以下初始化没有区别
auto i=10;
auto j(10);
auto l{10};
//以下编译器不会提示类型丢失
int i=10.0;
int j(10.0);
// 编译器或会提示精度丢失,是一种更好的初始化方式
int l{10.0};
//c++ 11 的列表初始化
auto l={10,20,30}
initializer_list<int> m={10,20,30};

c++ 引用类型 和 c# 不同

类的栈空间分配

#include <iostream>
using namespace std;

class  Ca{
public:
  Ca(int a): a(a){
  }
  int a;
};

void f1(Ca ca ){
  ca.a+=1;
  cout <<"函数内," << ca.a << endl;
}

int main() {
 /* 和 c# 不同的是 Ca(100) 是分配在函数栈空间的 */
 Ca ca =  Ca(100);
 //函数 f1 传递的是值拷贝,和 c# 是不相同的概念
 f1(a); //函数内,101
 cout <<ca.a << endl; //100
}

类的栈空间分配,及性能优化引用类型

#include <iostream>
using namespace std;

class  Ca{
public:
  Ca(int a): a(a){
  }
  int a;
};

void f1(Ca &ca ){
  ca.a+=1;
  cout <<"函数内," << ca.a << endl;
}

int main() {
 /* 和 c# 不同的是 Ca(100) 是分配在函数栈空间的  ,如果构造函数不需要彻底参数或有默认值
     都不需要显示调用构造函数 */
 Ca ca =  Ca(100);
 //函数 f1 传递的引用类型,类似 c# 的 ref
 f1(ca); //函数内,101
 //因为函数内对  ca 进行了引用,在函数外可以反应修改的值
 cout <<ca.a << endl; //101
}

类的堆空间分配

#include <iostream>
using namespace std;

class  Ca{
public:
  Ca(int a): a(a){
  }
  int a;
};

void f1(Ca* ca ){
  // 👍 ca->a  等同于  (*ca).a
  ca->a+=1;
  cout <<"函数内," << ca->a << endl;
}

int main() {
 /* 通过指针, 指向堆空间,堆空间通过 new 分配 */
 Ca* ca =new  Ca(100);
 //函数 f1 传递的指针类型,和c# 的 引用类型一致
 f1(ca); //函数内,101
 //通过指针访问的是和函数内相同的堆空间
 cout << ca->a << endl; //101

 //堆空间,通过 new 分配记得要释放
 delete ca;
}

malloc、free

  1. new / new[]:完成两件事,先底层调用 malloc 分配了内存,然后调用构造函数(创建对象)。
  2. delete/delete[]:也完成两件事,先调用析构函数(清理资源),然后底层调用 free 释放空间。
  3. new 在申请内存时会自动计算所需字节数,而 malloc 则需我们自己输入申请内存空间的字节数。
//申请的时候必须要指定申请的的大小
char *str = (char*) malloc(100);
free(str);
str = nullptr;

引用类型丢失

class  Ca{
public:
  Ca(string  &message): msg(message){
  }
  //引用外部的变量
  string &msg;

  void print(){
    cout << ((msg != "") ? "输出了":"没有值")  << endl;
  }
};

/* 此函数执行结束 堆栈丢失 msg 变量不存在了 ,ca  对象引用了不存在的 对象 msg */
Ca* createCa(){
  string  msg="msg";
  return new Ca(msg);
}

int main() {
   auto ca= createCa();
   ca->print(); // ❌ 没有值
   delete ca;
   return 0;
}

对象切割及静态联编导致的多态失效

class Ca {
public:
  virtual void  f()   {
    cout << "Ca::f" << endl;
  }
};

//在内存上 cb 拷贝了 ca  的部分
class Cb : public Ca {
public:
    void f() override  {
    cout << "Cb::f" << endl;

  }
};

int main() {
  Ca a=Cb(); a 只能找到 自己类型的部分,因为不是指针,即使使用 virtual 标注方法
  a.f(); // ❌ "Ca::f" 对象切割导致的多态丢失
  return 0;
}

静态联编

class Ca {
public:
   void f() {
    cout << "Ca::f" << endl;
  }
};

class Cb : public Ca {
public:
  void f()  {
    cout << "Cb::f" << endl;
  }
};


int main() {
  //因为没有虚函数,在编译的时候使用了静态联编 ,a 指针指向的内容在编译的时候就确定了是 Ca 类型的
  //而不是 Cb 类型的,所以调用的是 Ca 类型的 f 函数,编译器提前就确定了调用的是 Ca 类型的 f 函数
  //而不管实际运行的是 Cb 类型的 f 函数
  Ca* a = new Cb();
  a->f(); //❌ "Ca::f" 对象切割导致的多态丢失
  delete a;
  return 0;
}

动态联编

动态联编,c++是编译成机器吗的,为什么能做到动态的确定虚函数指向动态对象呢,通过虚函数表实现的

class Ca {
public:
  virtual void f() {
    cout << "Ca::f" << endl;
  }
};

class Cb : public Ca {
public:
  void f() override {
    cout << "Cb::f" << endl;
  }
};


int main() {
  //因为 a 的 f 方法是虚函数使用动态联编,在运行的时候才确定类型
  Ca* a = new Cb();
  a->f(); 👌 "Cb::f" 对象切割导致的多态丢失
  delete a;
  return 0;
}

智能指针

class Ca {
public:
  Ca() {
    cout << "Ca constructor" << endl;
  }
  ~Ca() {
    cout << "Ca destructor" << endl;
  }
};

共享指针 shared_ptr

标准库为我们提供的共享指针 shared_ptr 也可以自动释放关联的堆空间。共享指针可以被多个使用者 持有,共享指针内部会记录持有者的个数(被引用的次数),随着应用程序的运行,持有者会一个个诞 生,一个个消亡,当共享指针不再存在持有者时(引用计数为 0 时),共享指针所关联的堆内存被释 放

#include <memory>;

int main() {
 {
    //shared_ptr  make_shared 共享指针
    shared_ptr<Ca> myClass1 = make_shared<Ca>();
    cout << myClass1.use_count()<< endl; // 1
    // myClass1 原来关联的共享指针内部会记录持有者的个数为零,进行堆内存释放
    // myClass1.reset(); 也可以手动是否关联的内存
    myClass1 = make_shared<Ca>();
    cout << myClass1.use_count()<< endl;
  }
  return 0;
}

Ca constructor
1
Ca constructor
Ca destructor
1
Ca destructor

使用指针的话需要手动的是否内存

Ca* ca{ new Ca() };
delete ca;

ca = new Ca();

delete ca;

独占指针 unique_ptr

int main() {
  {
    //定义独占指针
    unique_ptr<Ca> myClass1 = make_unique<Ca>();
    //不允许来个独占指针指向相同的内存空间
    //unique_ptr<Ca> myClass2 = myClass1;

    //当独占指针指向别的内存空间,原来的内存空间会释放
    myClass1= make_unique<Ca>();
  }

  return 0;
}

模板

函数模板

template <typename T>
inline T const& Max (T const& a, T const& b)
{
  return a < b ? b : a;
}

类模板

template <class T>
class Stack {
private:
    vector<T> elems;          // 向量,用来保存元素

public:
    void push(T const&);      // 入栈
    void pop();               // 出栈
    T top() const;            // 返回栈顶元素
    bool empty() const{       // 如果为空则返回真
        return elems.empty();
    }
};

指针

int main() {
   int* a= new int(10);
   int *b = new int(20);

   // 等同于 int* c = nullptr; 或 int* c = 0; 不指向任何内存地址
   int* c = NULL ;
   //野指针,指向的是随机的内存地址
   int* d;


   cout<<"a=" << *a << endl; // a=10
   cout<<"b=" << *b << endl; // b=20
   //不能直接访问 空指针,会引发错误
   if( c!= nullptr){
     cout<<"c=" << *c << endl;
   }
   cout<<"d=" << *d << endl; //输出随机的值
  return 0;
}

指针在不同的操作系统,指针本身的字节长度也不一样,32 操作系统是 4 字节 64 位操作系统是 8 字 节

int main() {
  int* a=  new int(10);
  double* b=  new double (10);

  cout << "a: " << sizeof(a) << endl;
  cout << "b: " << sizeof(b) << endl;
}

常量指针 和指针常量

* 后面的 const 是用来修饰指针的, * 前面的 const 是用来修饰变量的

int main() {
  //常量指针
  const int*  const p1=new int(10);
  // 不可以修改指针的指向
  //p1 = new int(20);

  // 指针指向常量,不可以继续修改
  const int*  p2=new int(10);
  //*p2 =30;
}

数组

int main() {
  //定义一个 int 数组指向数组
  int* a= new int[3]{10,20,30};

  //这里如果进行越界范围,不会报错
  for (int i = 0; i < 3; ++i) {
    cout << *a << endl;
    a++;
  }

  //删除数组堆空间
  delete  [] a;

  return 0;
}

函数


#include <functional>

void  f(int a) {
  std::cout << " f action:" << a << std::endl;
}

// 等同于 void (*callback)(int a)  或 std::function<void(int)> callback
// std::function<void(int)>  这种方式更好
void  f1( void callback(int a) ) {
  callback(1);
}

int main() {
  //等同于  f1(&f)
  f1(f);
  return 0;
}

lambda

#include <functional>
#include <iostream>

void f2(std::function<int(int)> callback) {
  auto item=  callback(1);
  std::cout << " f2 action:" << item << std::endl;
}

int main() {
  //等同于  f1(&f)
  auto item=100;

  //lambda 表达式 通过 [&item] 捕获外部的变量
  // 参数类型如果能进行自动转换的,就可以匹配
  auto tmp=[&item](int a)->bool {
    std::cout << " f action:" << a <<" capture:"<< item << std::endl;
    return false;
  };

  f2(tmp);
  return 0;
}

union

union 存在内存对齐。在 64 位处理器中,内存对齐的单位一般是 8 字节 union 用成员的最大地址空 间表示 union 的 内存空间大小,但是一个 union 对象只能是唯一的类型

//通过 union 做到一块内存空间,既可以代码 ip4 也可以表示 ip6 的内存空间
union  MyIp{
  char ipv4[4];
  char ipv6[16];
};

int main() {
  cout << sizeof(MyIp) << endl; //16
  MyIp ip={.ipv4={'1','2','3','4'}};
  cout << ip.ipv6 << endl; //1234
  cout << ip.ipv4 << endl; //1234
  return 0;
}

静态变量

类中的静态变量

class  Ca{
public:
  //类中的静态变量不能在构造函数里进行初始话,也不能在定义的时候进行初始化
  static int A;
  Ca(){

  }
};

//类中的静态变量只能这样进行初始化,因为静态变量有静态存储区进行保存
int Ca::A = 100;

int main() {
   auto ca =new Ca();
   cout << ca->A << endl; //100
   ca->A= 200;
   cout << ca->A << endl; //200
   Ca::A = 300;
   cout << Ca::A << endl; //300
   cout << ca->A << endl; //300
   delete ca;
   cout << "---------------------" << endl;
   // delete 删除了 ca 对象的内存,但是静态存储区是不受影响的,静态存储区的生命周期是程序
   cout << Ca::A << endl; //300
   //这里的 ca 指针本身还在,只是指针指向的内存区域不存在的,但是静态变量存在
   cout << ca->A << endl; //300
   return 0;
}

静态变量不受 {} 作用域影响,生命周期直到程序运行结束

class  Ca{
public:
  Ca() {
    cout << "Ca" << endl;
  }
  ~Ca() {
    cout << "~Ca" << endl;
  }
};

int main() {
   {
     static Ca ca;
   }
   cout << "main" << endl;
   return 0;
}

Ca
main
~Ca

友元

如下代码通过 public 函数 f 获取 private 的变量 a 的值

友元函数

class  Ca{
public:
  // f 函数的 const 表示此函数内不能修改此类内的任何成员值
  int  f() const{
     return a;
  };

private:
  int a=200;
};

int main() {
  auto ca=Ca();
  int i= ca.f();
  cout << i << endl;
  return 0;
}

通过友元函数实现访问 友元函数只是一个普通函数,并不是该类的类成员函数,它可以在任何地方调 用, 通过对象不能使用友元函数

class  Ca{
public:
private:
  int a=200;
  //定义友元函数
  friend int  f(Ca& ca);
};

//友元函数的实现
int f(Ca& ca){
  //可以访问任何对象的成员,不受访问权限限制
  return ca.a;
}

int main() {
  auto ca=Ca();
  int i= f(ca);
  cout << i << endl;
  return 0;
}

友元类

  1. 友元关系没有继承性 假如类 B 是类 A 的友元,类 C 继承于类 A,那么友元类 B 是没办法直接访 问类 C 的私有或保护成员。
  2. 友元关系没有传递性 假如类 B 是类 A 的友元,类 C 是类 B 的友元,那么友元类 C 是没办法直 接访问类 A 的私有或保护成员,也就是不存在“友元的友元”这种关系。
class  Ca{
public:
private:
  int a=200;
  //定义可以把保护成员开发给的类
  friend class  Cb ;
};

class  Cb{
public:
   //可以访问受保护的成员
   int getA(Ca &ca){
      return ca.a;
    }
};

int main() {
  auto cb=Cb();
  auto ca=Ca();
  int i=cb.getA(ca);
  cout << i << endl;
  return 0;
}

decltype

decltype (expression)

这里的括号是必不可少的,decltype 的作用是“查询表达式的类型”,因此,上面语句的效果是,返回 expression 表达式的类型。注意,decltype 仅仅“查询”表达式的类型,并不会对表达式进行“求值”。

int main() {
  auto a=100;
  auto b=100.1;
  // decltype 推动出类型是 float
  decltype(a+b) c=200.1;
  cout << c << endl;
  return 0;
}

与 using/typedef 合用,用于定义类型

int main() {
  auto a=100;
  auto b=100.1;
  //定义类型
  using  f  =decltype(a+b);
  //定义类型
  typedef decltype(a+b) nf;
  f  d=100.1;
  nf e=100.1;
  cout << d << endl;
  cout << e << endl;
  return 0;
}

模板里定义返回的类型

template <typename T>
auto multiply(T x, T y)->decltype(x*y)
{
    return x*y;
}

线程

五种创建线程的方式

  1. 函数指针
  2. Lambda 函数吧
  3. Functor(仿函数)
  4. 非静态成员函数
  5. 静态成员函数
#include <thread>

/* 函数指针  */
void fun(int x) {
  while (x-- > 0) {
    cout << x << endl;
  }
};

int main() {

  /* Lambda */
  auto fun= [](int x){
    while (x-- > 0) {
      cout <<"lambda "<< x << endl;
    }
  };
  std::thread t1(fun,10);

  t1.join();
  return  0;
}

防函数操作符重载

#include <thread>

class Ca {
public:
  Ca(){
    std::cout << "Ca constructor" << std::endl;
  }
  void operator()(int x) {
    std::cout << "operator action" << std::endl;
    while (x-- > 0) {
      cout << "lambda " << x << endl;
    }
  }
};

int main() {
  auto ca= Ca();
  std::thread t1(ca, 10);
  t1.join();
  return 0;
}

非静态成员

#include <thread>

class Ca {
public:
  Ca(){
    std::cout << "Ca constructor" << std::endl;
  }
  void fun(int x) {
    while (x-- > 0) {
      cout << x << endl;
    }
  }
};

int main() {
  auto ca= Ca();
  std::thread t1(&Ca::fun, ca, 10);
  t1.join();
  return 0;
}

枚举

//限定作用域的枚举类型
enum class open_modes { input, output, append };
open_modes i=open_modes::input
//不限定作用域的枚举类型
enum color { red, yellow, green };
color j=red

stl

Alexander Stepanov(后被誉为 STL 标准模板库之父,后简称 Stepanov),1950 年出生与前苏联的 莫斯科,他曾在莫斯科大学研究数学,此后一直致力于计算机语言和泛型库研究。

STL 是 “Standard Template Library” 的缩写,中文译为 “标准模板库”。STL 是一些容器的集合,例 如 list、vector、set、map 等。同时,STL 也是算法和其它一些组件的集合。STL 的目的是标准化组 件,这样就可以使用现成的组件,不用重新开发。 STL 就是借助模板把常用的数据结构及其算法都实 现了一遍,并且做到了数据结构和算法的分离。例如,vector 的底层为顺序表(数组),list 的底层 为双向链表,deque 的底层为循环队列,set 的底层为红黑树, hash_set 的底层为哈希表。

在掌握了基础语法后,工作中要要使用的是这些开发库 C++ STL 构成 C++ STL 包含三大部分:容器 (Containers)、算法(Algorithms)、迭代器(iterators)。

  1. 容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。| 2.算法 作用于容器,它们提供了执行各种操作的方式,包括对 容器内容执行初始化、排序、搜索和转换等操作。
  2. 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集 在 C++ 标准中,STL 被组织为下面的 13 个头文件
<algorithm>
<deque>
<functional>
<iterator>
<vector>
<list>
<map>
<memory>
<numeric>
<queue>
<set>
<stack>
<utility>
👍🎉🎊