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 }
- 两个无符号类型相减,也要注意结果是否为负数的情况- 字面值常量:每种字面值常量都对应一种基本类型,比如 'a' 对应 char,2 对应 int 等;特别的,需要注意字符和字符串的区别,单引号括起来的单个字符称为字符字面量;双引号括起来的字符(0个或多个)称为字符串字面量。- 字符串字面量在末尾会多一个换行符,其本质是字符数组 char*```cppchar *i = "abc";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; // 正常
- 变量定义时没给初始值,会默认初始化。一种特殊情况是定义在函数内部的基本类型变量如果不显示初始化,则不会被默认初始化,未被初始化的变量不能被访问,否则报错- 变量声明和定义是两回事,一个变量只能被定义一次,但可以被声明多次```cppextern int i; // 声明而非定义一个变量int j; // 声明并定义
C++ 代码模式是一个头文件+一个源文件,其中头文件负责声明变量或函数或类,源文件负责定义并实现
2、复合类型
引用
int a=12int &d=a; // 通过 &x 的形式定义一个引用,引用只是一个别名d=13; // a 变为13int c=d; // c 为13int &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; // 初始值为 空指针
<a name="K3JAm"></a>### 3、const & auto & decltype- 当用 const 修饰变量的类型时,那么该变量的值不能被改变;且 const 修饰的变量必须显示初始化```cppconst int a = 10;a = 20; // 错误const int b; // 错误,需要显示初始化int b=1;const int &c=b; // 常量引用c = 2; // 错误
常量指针,用顶层const表示指针本身是常量,底层const表示其指向的对象是常量
double pi = 3.14;const double *const pip = π // 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
- 如果不知道一个值的类型,可以设置为 auto,让编译器计算时自动推导类型```cppauto item = val + val2;auto sz=0,pi=3.14; // 错误,一条语句只能推导一个类型const int ci = 0;auto b = ci; // b是整数,不是常量。auto 会忽略顶层constconst auto b = ci; // b 是常量auto &g = ci; // g 是常量引用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; // 正确,注意双层括号永远得到的是引用
- 可以使用 struct 定义自己的类型```cppstruct Book{std::string bookNo;unsigned units_sold=0;double revenue = 0.0;};Book book,*booptr;
4、string
标准库类型之一,用于支持可变长字符串操作,使用时需要引用头文件 string.h
#include <string>using namespace std::string;// 初始化操作string s;string s1(s);string s2 = s;string s3("abc"); // s3是 “abc” 副本,除了最后一个空字符外string s4 = "abc"; // 和上一个等价string s5(n,'c');// 常用操作os<<s;is>>s;getline(is,s);s.empty(); // 返回 size_type 类型s.size();s[n];// 字符串字面量不能直接相加string s = "hello" + "world"; // 错误string s = "hello" + string("world");// 对每个字符的判断,需要借助 cctype 标准库定义的函数isalnum(a); // a 是字母或是数字isupper(a); // a是大写字母toupper(a); // 转为大写// 范围 for 语句遍历 stringstring str("Hello World");for(auto a:str){if(isupper(a))cout<<a<<endl;}
5、vector
标准库类型之一,用于支持可变长集合操作,使用时需要引用头文件 vector.h
#include <vector>using namespace std::vector;// vector 是一个类模版vector<int> ivec; // ivec 保存 int 类型的对象vector<vector<int>> file; // 元素是 vector// 初始化vector<T> v; // 元素类型为 T 的 vector,会执行默认初始化vector<T> v2(v);vector<T> v2 = v;vector<T> v3(n,val); // n 个 val 填充 v3vector<T> v4{a,b,c,d};vector<T> v5 = {a,b,c};// 常用操作v.empty();v.size();v.push_back(t);v.pop_back();v.erase(pos); // 删除迭代器 pos 处的值v.erase(start,end);v.clear(); // 清空v[n];v = {a,b,c,d};// 范围 for 语句遍历 vectorvector<int> v{1,2,3,4,5};for(auto &a:v){a *=a;}for(auto b:v){cout<<b<<endl;}
6、迭代器
标准库类型之一,常用在 string 或 vector 的遍历中
auto b = v.begin(),e = v.end();auto cb = v.cbegin(); ce = v.cend(); // 常量迭代器
7、内置数组
和 vector 很像,但有一点,其 size 不可动态变化
int a[10];int cnt = 12;int b[cnt]; // 错误,下标必须是常量表达式int b[2] = {1,2};string b[10] = {"abc"};// 字符数组特殊性char a3[] = "C++"; // a3 实际包含四个字符,末尾有 "\0"// 数组不允许拷贝和赋值int a[]= {1,2};int a2[] = a; // 错误a2=a; // 错误
8、C 风格的字符串
这种字符串有个特征,就是以 ‘\0’ 结尾,cstring是操作该字符串的一个标准库
#include <cstring>char p[] = "abc";strlen(p);strcmp(p1,p2);strcpy(p1,p2);strcat(p1,p2);
9、显示的类型转换
// static_cast 不能转换掉 const 性质const char *cp; // cp 是指向常量字符的非常量指针(底层const)char *p = static_cast<char*>(cp); // 错误static_cast<string>(cp); // 正确// const_cast 不能转换原来的类型const_cast<string>(cp); // 错误
三、函数和类
1、函数
- 当用实参初始化const 形参时,会忽略掉 const,因此定义函数重载时要注意 ```cpp void fun(const int a){}
int b=2; fun(b); // 正确
void fun(int a){} // 错误,重复定义
2、把不会改变的形式参数定义为常量引用是一个好的实践<br />3、如果事先不知道要传递多少个形式参数,可以通过 initializer_list```cppvoid error_msg(initializer_list il){for(auto beg=il.begin();beg!=il.end();++beg){cout<<*beg<<endl;}}
4、函数的返回值
// 1、不能是局部对象或局部指针// 2、可以返回列表初始化vector<string> process(){if(xxx)return {};if(xxx)return {"a","b"};}// 3、由于数组不能被拷贝,所以函数不能返回数组,但可以返回数组指针using T = int[10];T* func(int i);
5、函数实参默认值
string screen(int ht=24,int wid=80,char bg='');
6、使用 inline 构造内联函数,具有更好的性能,一般放在头文件里
inline const string & shortestString(const string&s1,const string &s2);
7、函数指针
函数的类型由返回值和参数共同决定
// 函数类型为 bool(const string &s1,const string &s2)bool lengthCompare(const string &s1,const string &s2);// pf 是一个函数指针bool (*pf)(const string &s1,const string &s2);// 当把函数名当作值使用时,会自动转换成指针pf = lengthCompare; // pf 时函数指针pf = &lengthCompare; // 等价// decltype 作用于函数时,总是取得函数类型,而非指针类型
2、类
使用关键字 class 定义类,struct 可以定义数据结构,其成员都是 public 的
const 成员函数
// 默认情况下,this 是一个非常量的常量指针,为了使其成为指向常量的常量指针,可以用conststring Sales_data::isbn() const {return this->isbn;}
定义一个类
class Sales_data{// 公有函数public:// 构造函数Sales_data() = default; // 默认构造函数Sales_data(const string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p * n) {} // 重载,列表初始值Sales_data(const string &s) : bookNo(s) {} // 重载Sales_data(istream &);// 成员函数string isbn() const { return bookNo; }Sales_data &combine(const Sales_data &data);// 私有函数private:double avg_price() const{return units_sold ? revenue / units_sold : 0;}string bookNo;unsigned units_sold;double revenue;};
隐式的类类型转换 ```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”));// 错误
- 字面值常量类```cpp// 通过以 constexpr 做构造函数,其成员必须都是字面值
- 静态成员 ```cpp // 1、静态成员是共有的,不是某个实例独有
// 2、静态成员和 this 没什么关系,因此不能声明为 const
// 3、仍可以通过实例或指针或引用访问静态成员
// 4、static 关键字只能出现在类内,因此类外定义static函数时不能再使用 static
// 5、静态成员可以作为默认实参,可以是不完全类型
<a name="yiORv"></a>## 四、动态内存- C++内存分为动态内存、静态内存和栈内存,其中动态内存通过 new 和 delete 操作来控制。- 动态内存的释放是一个不太好管理的操作,为此标准库提供了两种智能指针:shared_ptr,unique_ptr,都定义在 memery 头文件中,用来代替 new delete 操作- 基本用法```cpp#include <memory>std::shared_ptr<int> ptra = std::make_shared<int>(a);std::shared_ptr<int> ptra2(ptra); //copystd::cout << ptra.use_count() << std::endl; // 2std::unique_ptr<int> uptr(new int(10)); //绑定动态对象std::unique_ptr<int> uptr2 = uptr; //不能赋值std::unique_ptr<int> uptr2(uptr); //不能拷贝std::unique_ptr<int> uptr2 = std::move(uptr); // 转换所有权uptr2.release(); //释放所有权uptr2.reset(); // 销毁
- 注意点
1、当object第一次创建的时候,就要立刻交由一个shared_ptr管理,其他的shared_ptr和weak_ptr都必须从第一个shared_ptr拷贝或赋值得到。具体来说,就是调用shared_ptr(raw ptr)和make_shared函数。
class Thing;shared_ptr<Thing> p1(new Thing);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的情况下抛出异常。
