Node.js 中的 addon 是由 C++ 语法制作的,通过 node-gyp 编译的,可以运行在 node 环境下的一种技术,本篇文章记录 C++ 语言的基础(前端JS视角,相通的地方没列举)

一、扫盲

1、C++ 没有运行时环境,代码需要编译成二进制代码才能执行,编译工具比较多,如:Mingw-w64 是 GCC 的 Windows 版本,clang 是 Mac 下的主流编译器
2、window 开发环境搭建:https://www.huaweicloud.com/articles/f5e0b8de8f5659c6389154c429bfb6ab.html
mac 开发环境搭建:https://zhuanlan.zhihu.com/p/48233069
3、三方依赖管理:https://ccup.github.io/conan-docs-zh/
4、如果使用 VSCode 作为 IDE,则可以安装 Code Runner 扩展工具,可以很方便的运行 C++ 代码
5、C++ 表达式需要用分号作为结束

二、基础语法

1、基本类型

  • 有 short,int,long,float,double,char,bool,void 等基本类型,外加 char 的几种变种 wchar_t,char16_t,char32_t
  • 可以在部分类型前加 unsigned 表示无符号型,即不包含负数区间
  • 不同类型之间互相赋值会发生类型转换,特别的,如果一个 int 和一个 unsigned 类型相加,则会把 int 转为无符号型后再相加,这往往不是希望的: ```cpp

    include

    using namespace std;

int main(int argc, char const *argv[]) { unsigned u = 10; int i = -42; cout << u + i; // 4294967264 }

  1. - 两个无符号类型相减,也要注意结果是否为负数的情况
  2. - 字面值常量:每种字面值常量都对应一种基本类型,比如 'a' 对应 char2 对应 int 等;特别的,需要注意字符和字符串的区别,单引号括起来的单个字符称为字符字面量;双引号括起来的字符(0个或多个)称为字符串字面量。
  3. - 字符串字面量在末尾会多一个换行符,其本质是字符数组 char*
  4. ```cpp
  5. char *i = "abc";
  6. cout << i;
  • 变量的列表初始化(C++11 推荐写法),如果发生精度丢失会报错 ```cpp int a = 1; int b = {1}; // 列表初始化 int c{1}; // 列表初始化 int d(1);

double id = 3.14; int a{id},b={id}; // 报错 int c(id),d=id; // 正常

  1. - 变量定义时没给初始值,会默认初始化。一种特殊情况是定义在函数内部的基本类型变量如果不显示初始化,则不会被默认初始化,未被初始化的变量不能被访问,否则报错
  2. - 变量声明和定义是两回事,一个变量只能被定义一次,但可以被声明多次
  3. ```cpp
  4. extern int i; // 声明而非定义一个变量
  5. int j; // 声明并定义
  • C++ 代码模式是一个头文件+一个源文件,其中头文件负责声明变量或函数或类,源文件负责定义并实现

    2、复合类型

  • 引用

    1. int a=12
    2. int &d=a; // 通过 &x 的形式定义一个引用,引用只是一个别名
    3. d=13; // a 变为13
    4. int c=d; // c 为13
    5. int &e; // 错误,引用必须被初始化
  • 指针 ```cpp int t; // 通过 定义一个指针,指向 int 的指针,定义时可以不初始化;

int val = 42; int p = &val; // p 存放的是42在内存中的地址,而不是具体的值42,这个 & 是取地址运算符,不是引用 cout<< p; // 42,利用 * 运算符做解引用操作,可以取到p指向的值

double db; int *p1 = &db; // 错误,指针指向的类型必须一致

int *p2 = nullptr; // 初始值为 空指针

  1. <a name="K3JAm"></a>
  2. ### 3、const & auto & decltype
  3. - 当用 const 修饰变量的类型时,那么该变量的值不能被改变;且 const 修饰的变量必须显示初始化
  4. ```cpp
  5. const int a = 10;
  6. a = 20; // 错误
  7. const int b; // 错误,需要显示初始化
  8. int b=1;
  9. const int &c=b; // 常量引用
  10. c = 2; // 错误
  • 常量指针,用顶层const表示指针本身是常量,底层const表示其指向的对象是常量

    1. double pi = 3.14;
    2. const double *const pip = &pi; // pip 是一个指向常量的常量指针,pip自身不能变,其指向的对象也不能变
  • 常量表达式 constexpr ```cpp constexpr int mf = 20; constexpr int limit = mf + 1; // 正确 constexpr int sz = size(); // 只有当 size 也是常量表达式时才正确

constexpr int *p = nullptr; // p为常量指针,当 constexpr 修饰指针时,会把指针置为顶层const

  1. - 如果不知道一个值的类型,可以设置为 auto,让编译器计算时自动推导类型
  2. ```cpp
  3. auto item = val + val2;
  4. auto sz=0,pi=3.14; // 错误,一条语句只能推导一个类型
  5. const int ci = 0;
  6. auto b = ci; // b是整数,不是常量。auto 会忽略顶层const
  7. const auto b = ci; // b 是常量
  8. auto &g = ci; // g 是常量引用
  9. auto &h = 42; // 错误,设置一个类型为auto引用时,初始值中的常量属性会保留
  • 当希望从表达式中获取类型,但是不想用表达式的值作为初始值时,可以用 decltype ```cpp decltype(f()) sum = x; // 使用 f() 的类型,但是不关心 f() 的值

const int ci = 0, &cj = ci; decltype(ci) a = 0; // a 是 const int 类型,decltype保留表达式的原有属性 decltype(cj) b = a; // b 是 const int & 类型

int i =42, p = &i; decltype(p) c; // 错误,c是int & 类型;decltype的表达式如果是解引用,将得到引用类型

decltype((i)) d; // 错误,d是引用,需初始化 decltype(i) d; // 正确,注意双层括号永远得到的是引用

  1. - 可以使用 struct 定义自己的类型
  2. ```cpp
  3. struct Book{
  4. std::string bookNo;
  5. unsigned units_sold=0;
  6. double revenue = 0.0;
  7. };
  8. Book book,*booptr;

4、string

标准库类型之一,用于支持可变长字符串操作,使用时需要引用头文件 string.h

  1. #include <string>
  2. using namespace std::string;
  3. // 初始化操作
  4. string s;
  5. string s1(s);
  6. string s2 = s;
  7. string s3("abc"); // s3是 “abc” 副本,除了最后一个空字符外
  8. string s4 = "abc"; // 和上一个等价
  9. string s5(n,'c');
  10. // 常用操作
  11. os<<s;
  12. is>>s;
  13. getline(is,s);
  14. s.empty(); // 返回 size_type 类型
  15. s.size();
  16. s[n];
  17. // 字符串字面量不能直接相加
  18. string s = "hello" + "world"; // 错误
  19. string s = "hello" + string("world");
  20. // 对每个字符的判断,需要借助 cctype 标准库定义的函数
  21. isalnum(a); // a 是字母或是数字
  22. isupper(a); // a是大写字母
  23. toupper(a); // 转为大写
  24. // 范围 for 语句遍历 string
  25. string str("Hello World");
  26. for(auto a:str){
  27. if(isupper(a))
  28. cout<<a<<endl;
  29. }

5、vector

标准库类型之一,用于支持可变长集合操作,使用时需要引用头文件 vector.h

  1. #include <vector>
  2. using namespace std::vector;
  3. // vector 是一个类模版
  4. vector<int> ivec; // ivec 保存 int 类型的对象
  5. vector<vector<int>> file; // 元素是 vector
  6. // 初始化
  7. vector<T> v; // 元素类型为 T 的 vector,会执行默认初始化
  8. vector<T> v2(v);
  9. vector<T> v2 = v;
  10. vector<T> v3(n,val); // n 个 val 填充 v3
  11. vector<T> v4{a,b,c,d};
  12. vector<T> v5 = {a,b,c};
  13. // 常用操作
  14. v.empty();
  15. v.size();
  16. v.push_back(t);
  17. v.pop_back();
  18. v.erase(pos); // 删除迭代器 pos 处的值
  19. v.erase(start,end);
  20. v.clear(); // 清空
  21. v[n];
  22. v = {a,b,c,d};
  23. // 范围 for 语句遍历 vector
  24. vector<int> v{1,2,3,4,5};
  25. for(auto &a:v){
  26. a *=a;
  27. }
  28. for(auto b:v){
  29. cout<<b<<endl;
  30. }

6、迭代器

标准库类型之一,常用在 string 或 vector 的遍历中

  1. auto b = v.begin(),e = v.end();
  2. auto cb = v.cbegin(); ce = v.cend(); // 常量迭代器

7、内置数组

和 vector 很像,但有一点,其 size 不可动态变化

  1. int a[10];
  2. int cnt = 12;
  3. int b[cnt]; // 错误,下标必须是常量表达式
  4. int b[2] = {1,2};
  5. string b[10] = {"abc"};
  6. // 字符数组特殊性
  7. char a3[] = "C++"; // a3 实际包含四个字符,末尾有 "\0"
  8. // 数组不允许拷贝和赋值
  9. int a[]= {1,2};
  10. int a2[] = a; // 错误
  11. a2=a; // 错误

8、C 风格的字符串

这种字符串有个特征,就是以 ‘\0’ 结尾,cstring是操作该字符串的一个标准库

  1. #include <cstring>
  2. char p[] = "abc";
  3. strlen(p);
  4. strcmp(p1,p2);
  5. strcpy(p1,p2);
  6. strcat(p1,p2);

9、显示的类型转换

  1. // static_cast 不能转换掉 const 性质
  2. const char *cp; // cp 是指向常量字符的非常量指针(底层const)
  3. char *p = static_cast<char*>(cp); // 错误
  4. static_cast<string>(cp); // 正确
  5. // const_cast 不能转换原来的类型
  6. const_cast<string>(cp); // 错误

三、函数和类

1、函数

  • 当用实参初始化const 形参时,会忽略掉 const,因此定义函数重载时要注意 ```cpp void fun(const int a){}

int b=2; fun(b); // 正确

void fun(int a){} // 错误,重复定义

  1. 2、把不会改变的形式参数定义为常量引用是一个好的实践<br />3、如果事先不知道要传递多少个形式参数,可以通过 initializer_list
  2. ```cpp
  3. void error_msg(initializer_list il){
  4. for(auto beg=il.begin();beg!=il.end();++beg){
  5. cout<<*beg<<endl;
  6. }
  7. }

4、函数的返回值

  1. // 1、不能是局部对象或局部指针
  2. // 2、可以返回列表初始化
  3. vector<string> process(){
  4. if(xxx)
  5. return {};
  6. if(xxx)
  7. return {"a","b"};
  8. }
  9. // 3、由于数组不能被拷贝,所以函数不能返回数组,但可以返回数组指针
  10. using T = int[10];
  11. T* func(int i);

5、函数实参默认值

  1. string screen(int ht=24,int wid=80,char bg='');

6、使用 inline 构造内联函数,具有更好的性能,一般放在头文件里

  1. inline const string & shortestString(const string&s1,const string &s2);

7、函数指针
函数的类型由返回值和参数共同决定

  1. // 函数类型为 bool(const string &s1,const string &s2)
  2. bool lengthCompare(const string &s1,const string &s2);
  3. // pf 是一个函数指针
  4. bool (*pf)(const string &s1,const string &s2);
  5. // 当把函数名当作值使用时,会自动转换成指针
  6. pf = lengthCompare; // pf 时函数指针
  7. pf = &lengthCompare; // 等价
  8. // decltype 作用于函数时,总是取得函数类型,而非指针类型

2、类

使用关键字 class 定义类,struct 可以定义数据结构,其成员都是 public 的

  • const 成员函数

    1. // 默认情况下,this 是一个非常量的常量指针,为了使其成为指向常量的常量指针,可以用const
    2. string Sales_data::isbn() const {return this->isbn;}
  • 定义一个类

    1. class Sales_data
    2. {
    3. // 公有函数
    4. public:
    5. // 构造函数
    6. Sales_data() = default; // 默认构造函数
    7. Sales_data(const string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p * n) {} // 重载,列表初始值
    8. Sales_data(const string &s) : bookNo(s) {} // 重载
    9. Sales_data(istream &);
    10. // 成员函数
    11. string isbn() const { return bookNo; }
    12. Sales_data &combine(const Sales_data &data);
    13. // 私有函数
    14. private:
    15. double avg_price() const
    16. {
    17. return units_sold ? revenue / units_sold : 0;
    18. }
    19. string bookNo;
    20. unsigned units_sold;
    21. double revenue;
    22. };
  • 隐式的类类型转换 ```cpp item.combine(string(“abcd”));// 用 string 对象转为 Sales_data

// 只能转换一步 item.combine(“abcd”); // 错误,转了两次 =>string=>Sales_data

// 使用 explict 修饰构造函数可禁止默认转换 explict Sales_data(const string &s) : bookNo(s) {} // 重载 item.combine(string(“abcd”));// 错误

  1. - 字面值常量类
  2. ```cpp
  3. // 通过以 constexpr 做构造函数,其成员必须都是字面值
  • 静态成员 ```cpp // 1、静态成员是共有的,不是某个实例独有

// 2、静态成员和 this 没什么关系,因此不能声明为 const

// 3、仍可以通过实例或指针或引用访问静态成员

// 4、static 关键字只能出现在类内,因此类外定义static函数时不能再使用 static

// 5、静态成员可以作为默认实参,可以是不完全类型

  1. <a name="yiORv"></a>
  2. ## 四、动态内存
  3. - C++内存分为动态内存、静态内存和栈内存,其中动态内存通过 new 和 delete 操作来控制。
  4. - 动态内存的释放是一个不太好管理的操作,为此标准库提供了两种智能指针:shared_ptr,unique_ptr,都定义在 memery 头文件中,用来代替 new delete 操作
  5. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/1548284/1630845062093-8c2b5465-a944-4f6f-8cee-09e50173b427.png#clientId=u2f86ae4d-a283-4&from=paste&height=190&id=u529762a8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=379&originWidth=603&originalType=binary&ratio=1&size=120484&status=done&style=none&taskId=u97d2dbcd-288f-4f32-9c2d-d027cf994a2&width=301.5)
  6. - 基本用法
  7. ```cpp
  8. #include <memory>
  9. std::shared_ptr<int> ptra = std::make_shared<int>(a);
  10. std::shared_ptr<int> ptra2(ptra); //copy
  11. std::cout << ptra.use_count() << std::endl; // 2
  12. std::unique_ptr<int> uptr(new int(10)); //绑定动态对象
  13. std::unique_ptr<int> uptr2 = uptr; //不能赋值
  14. std::unique_ptr<int> uptr2(uptr); //不能拷贝
  15. std::unique_ptr<int> uptr2 = std::move(uptr); // 转换所有权
  16. uptr2.release(); //释放所有权
  17. uptr2.reset(); // 销毁
  • 注意点

1、当object第一次创建的时候,就要立刻交由一个shared_ptr管理,其他的shared_ptr和weak_ptr都必须从第一个shared_ptr拷贝或赋值得到。具体来说,就是调用shared_ptr(raw ptr)和make_shared函数。

  1. class Thing;
  2. shared_ptr<Thing> p1(new Thing);
  3. shared_ptr<Thing> p2(make_shared<Thing>());

2、能用裸指针解决问题的情况下,就不要使用智能指针
3、weak_ptr观察着shared_ptr管理的对象,必须从shared_ptr或weak_ptr创建。其唯一正确的使用方法是先用lock()调用提升为shared_ptr,然后使用shared_ptr。如果直接用shared_ptr(weak_ptr)来构造,可能会在weak_ptr已经expire的情况下抛出异常。