c++初始化过程那些事

默认初始化
什么是默认初始化?

默认初始化是指定义变量时没有指定初值时进行的初始化操作。例如 int a,Sales_data myData 等等。

这些变量被定义了而不是仅仅被声明(因为没有 extern 关键字修饰),而且没有显式的赋予初值。

特别的,如果采用动态分配内存的方式(即采用 new 关键字)创建的变量,不加括号时(如 int *p=new int;)也是默认初始化,加了括号(如 int *p=new int())为值初始化。

规则

变量的值与变量的类型与定义的位置有关系:

对于内置类型变量(如 int ,double,bool 等),定义于任何函数之外的变量被初始化为 0, 定义于函数体内部的内置类型将不被初始化,一个未被初始化的内置类型变量的值是未定义的.

对于类类型的变量(如 string 或 其他自定义类型),不管定义于何处,都会执行默认构造函数。如果该类没有默认构造函数,则会引发错误。因此,建议为每个类都定义一个默认构造函数(=default)。

例如, 对于在类中:

class Persion {
private:
std::string name; // 执行默认初始化过程
int age; // = 0
char sex; // = 0
};
在执行构造函数之前, 变量将首先进入默认初始化。 name 将首先使用默认构造函数进行初始化, 而 age, sex 将 被初始化为 0.

对于函数中:

int *pi=new int; // 默认初始化, *pi 的值为未定义。
int pi=new int(); // 值初始化为 0,pi 的值为 0
string *ps=new string; // 默认初始化 *ps 的值为空 string
string *ps=new string(); // 值初始化为空 string
值初始化
值初始化是值使用了初始化器(即使用了圆括号或花括号)但却没有提供初始值的情况。例如,int *p=new int(), vector vec(10); 等等都是典型的值初始化方式。

注意,当不采用动态分配内存的方式(即不采用 new 运算符)时,写成 int a(); 是错误的值初始化方式,因为这种方式声明了一个函数而不是进行值初始化。

如果一定要进行值初始化,必须结合拷贝初始化使用,即写成 int a=int(); 值初始化和默认初始化一样,对于内置类型初始化为 0,对于类类型则调用其默认构造函数,如果没有默认构造函数,则不能进行初始化。

直接初始化与拷贝初始化
直接初始化与拷贝初始化对应,其内部实现机理不同。

直接初始化是指采用小括号的方式进行变量初始化(小括号里一定要有初始值,如果没提供初始值,那就是值初始化了!)。

例如 :

int a(12);
Sales_data myData(para);
vector ivec(ivec2);
string s(“123456”);

拷贝初始化是指采用等号= 进行初始化的方式。

例如 :

int a=12;
string s=string(“123456”);

拷贝初始化看起来像是给变量赋值,实际上是执行了初始化操作,与先定义再赋值本质不同。

对于内置类型变量(如 int,double,bool 等),直接初始化与拷贝初始化差别可以忽略不计。

对于类类型的变量(如 string 或其他自定义类型),直接初始化调用类的构造函数(调用参数类型最佳匹配的那个),拷贝初始化调用类的拷贝构造函数。

特别的,当对类类型变量进行初始化时,如果类的构造函数采用了 explicit 修饰而且需要隐式类型转换时,则只能通过直接初始化而不能通过拷贝初始化进行操作。

拷贝构造函数和赋值运算符
在默认情况下(用户没有定义,但是也没有显式的删除),编译器会自动的隐式生成一个拷贝构造函数和赋值运算符。但用户可以使用 delete 来指定不生成拷贝构造函数和赋值运算符,这样的对象就不能通过值传递,也不能进行赋值运算。

class Person
{
public:

Person(const Person& p) = delete;

Person& operator=(const Person& p) = delete;

private:
int age;
string name;
};
上面的定义的类 Person 显式的删除了拷贝构造函数和赋值运算符,在需要调用拷贝构造函数或者赋值运算符的地方,会提示无法调用该函数,它是已删除的函数。 还有一点需要注意的是,拷贝构造函数必须以引用的方式传递参数。这是因为,在值传递的方式传递给一个函数的时候,会调用拷贝构造函数生成函数的实参。如果拷贝构造函数的参数仍然是以值的方式,就会无限循环的调用下去,直到函数的栈溢出。

何时调用

拷贝构造函数和赋值运算符的行为比较相似,都是将一个对象的值复制给另一个对象;但是其结果却有些不同: 拷贝构造函数使用传入对象的值生成一个新的对象的实例,而赋值运算符是将对象的值复制给一个已经存在的实例。

这种区别从两者的名字也可以很轻易的分辨出来,拷贝构造函数也是一种构造函数,那么它的功能就是创建一个新的对象实例;赋值运算符是执行某种运算,将一个对象的值复制给另一个对象(已经存在的)。

调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。

调用拷贝构造函数主要有以下场景:

对象作为函数的参数,以值传递的方式传给函数。 
对象作为函数的返回值,以值的方式从函数返回
使用一个对象给另一个对象初始化
class Person
{
public:
Person(){}
Person(const Person& p)
{
cout << “Copy Constructor” << endl;
}

Person& operator=(const Person& p)
{
    cout << "Assign" << endl;
    return *this;
}

private:
int age;
string name;
};

void f(Person p)
{
return;
}

Person f1()
{
Person p;
return p;
}

int main()
{
Person p;
Person p1 = p; // 1
Person p2;
p2 = p; // 2
f(p2); // 3

p2 = f1();        // 4

Person p3 = f1(); // 5

getchar();
return 0;

}
分析如下:

这是虽然使用了”=”,但是实际上使用对象 p 来创建一个新的对象 p1。也就是产生了新的对象,所以调用的是拷贝构造函数。
首先声明一个对象 p2,然后使用赋值运算符”=”,将 p 的值复制给 p2,显然是调用赋值运算符,为一个已经存在的对象赋值 。
以值传递的方式将对象 p2 传入函数 f 内,调用拷贝构造函数构建一个函数 f 可用的实参。
这条语句拷贝构造函数和赋值运算符都调用了。函数 f1 以值的方式返回一个 Person 对象,在返回时会调用拷贝构造函数创建一个临时对象 tmp 作为返回值;返回后调用赋值运算符将临时对象 tmp 赋值给 p2.
按照 4 的解释,应该是首先调用拷贝构造函数创建临时对象;然后再调用拷贝构造函数使用刚才创建的临时对象创建新的对象 p3,也就是会调用两次拷贝构造函数。不过,编译器也没有那么傻,应该是直接调用拷贝构造函数使用返回值创建了对象 p3。
列表初始化
列表初始化是 C++ 11 新引进的初始化方式,它采用一对花括号(即{})进行初始化操作。能用直接初始化和拷贝初始化的地方都能用列表初始化,而且列表初始化能对容器进行方便的初始化,因此在新的 C++标准中,推荐使用列表初始化的方式进行初始化。

列表初始化的应用场景有:

int a{12};
string s{“123”};
vector vec{1,2,3};
这里一定要注意,列表初始化使用的是花括号而不是圆括号!

nodejs绑定C++库

工具链

  1. nodejs
  2. node-gyp
    https://github.com/nodejs/node-addon-api/blob/main/doc/setup.md

调试

  1. Cmd+Shift+P 输入 configure task 配置一个任务,该任务会执行 npm run rebuild:dev,生成带调试信息的 node 扩展文件。笔者的配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "dev",
"group": "rebuild",
"problemMatcher": [],
"label": "npm: dev",
"detail": "node-gyp configure build --debug"
}
]
}
  1. 点击 debug 按钮之后,下面在 launch.json 中配置调试 node 扩展的任务,注意在配置的时候增加一个 preLaunchTask 任务,该任务就是我们上一步配置的。最终 luanch.json 配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"version": "0.2.0",
"configurations": [{
"type": "lldb",
"request": "launch",
"name": "Debug",
"preLaunchTask": "npm: dev",
"program": "/Users/huangzz/.nvm/versions/node/v18.0.0/bin/node",
"args": [
"/Users/huangzz/Documents/Project/N-API/node-addon/index.js"
]
}]
}

从 launch.json 可以看到整个调试的过程为:vscode 插件调用 lldb,启动 nodejs 去执行 index.js,在 js 文件中会调用 node 扩展,而该部分扩展已经包含了调试信息,故而可以用于调试。

文章

https://yunnysunny.gitbooks.io/nodebook/content/10_node_addon.html
https://academy.realm.io/cn/posts/oredev-kenneth-geisshirt-extending-node-js-cpp/

C++扩展源码示例

  1. https://github.com/kneth/DemoNodeExtension

V8 文档

  1. https://v8.dev/

node-addon-api 文档

https://github.com/nodejs/node-addon-api#api-documentation

electron创建本地服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const { app, BrowserWindow } = require('electron')
const PORT = 8080

// electron main
console.log(process.versions);

app.on("ready", function () {
var mainWindow = new BrowserWindow({ show: true });
mainWindow.loadURL("file://" + __static + "/app.html");
mainWindow.webContents.openDevTools()
mainWindow.webContents.once("did-finish-load", function () {
var http = require("http");
var server = http.createServer(function (req, res) {
console.log(req.url)
if (req.url == '/123') {
res.end(`ah, you send 123.`);
} else {
const remoteAddress = res.socket.remoteAddress;
const remotePort = res.socket.remotePort;
res.end(`Your IP address is ${remoteAddress} and your source port is ${remotePort}.`);
}
});
server.listen(PORT);
console.log("http://localhost:"+PORT);
});
});

nginx下try-files说明

root、alias 与 try_files 的配置与应用。

root 与 alias 的区别

root 与 alias 的区别主要在于 Nginx 如何解释 location 后面的路径的 URI,这会使两者分别以不同的方式将请求映射到服务器文件上。具体来看:

root 的处理结果是:root 路径+location 路径
alias 的处理结果是:使用 alias 路径替换 location 路径
例如,如下的 Nginx 配置:

1
2
3
4
5
6
7
server {
listen 9001;
server_name localhost;
location /hello {
root /usr/local/var/www/;
}
}

在请求 http://localhost:9001/hello/时,服务器返回的路径地址应该是/usr/local/var/www/hello/index.html

当使用 alias 时:

1
2
3
4
5
6
7
server {
listen 9001;
server_name localhost;
location /hello {
alias /usr/local/var/www/;
}
}

在请求 http://localhost:9001/hello/时,服务器返回的路径地址应该是/usr/local/var/www/index.html(用 alias 后面的路径将请求的 location 的地址 hello 替换掉)

另外,要注意的是,alias 路径后面必须使用/结束,否则无法匹配正确的路径,而 root 后则可有可无。所以建议都加上/,反之出错

try_files
接触到 try_files,是在对 vue-router 进行 History 模式进行配置时,官网给出的例子中使用到了 try_files:

1
2
3
location / {
try_files $uri $uri/ /index.html;
}

try_files 的作用就是在匹配 location 的路径时,如果没有匹配到对应的路径的话,提供一个回退的方案,在上面的例子中:$uri就代表匹配到的路径。例如我们访问/demo时,$uri 就是 demo

try_files 的最后一个参数是回退方案,前两个参数的意思是,保证如果能够匹配到路径的话访问匹配到的路径,例如访问/demo 时,如果根路径下没有 demo 目录,就会访问 index.html

要注意到的是,/index.html 的服务器路径,会到 root 路径下去寻找,上面的例子中,没有 root 选项,那么就会到 Nginx 的默认根路径下去寻找 index.html,如果设置了 root: a,那么就会到服务器的/a/index.html 路径下去匹配

在非根路径下使用 try_files
当我们希望在/test 路径下部署一个路由使用 History 的 Vue 应用,那么可以使用如下的 Nginx 配置:

1
2
3
4
5
6
7
8
9
server {
listen 9001;
server_name localhost;
location /test {
root /usr/local/var/www/hello/;
index index.html;
try_files $uri $uri/ /test/index.html;
}
}

这个时候:

非/test 路径下的请求,不会收到上面的配置的影响
当访问/test 时,会使用 root 的匹配规则,到服务器/usr/local/var/www/hello/test 路径下寻找 index.html 文件
当访问/test/demo1 时,会使用 try_files 的匹配规则,到 root 路径下去寻找最后一个参数/test/index.html 的回退方案,也就是说去/usr/local/var/www/hello/test 路径下寻找 index.html 文件
除了 Nginx 的配置外,Vue 应用本身还需要配置两处:

(1)vue-router 实例化时指定 history 模式,并添加 base 参数:

1
2
3
4
5
const router = new Router({
routes,
mode: 'history',
base: '/test'
});

(2)静态资源的目录 publicPath 设置为相对路径(否则回去绝对路径下寻找 JS 等静态资源)

1
2
3
{
assetsPublicPath: './'
}

参考

Nginx 的 location、root、alias 指令用法和区别@腾讯云
HTML5 History 模式@vue-router

APP优雅进行SSL证书校验——(一)服务器篇

https://zhuanlan.zhihu.com/p/36628482

生成公钥 pin

https://www.ssllabs.com/ssltest/analyze.html

公钥和证书 pin 介绍

https://m.freebuf.com/articles/endpoint/341407.html

https://medium.com/@Dranko/certificate-pinning-your-android-and-ios-apps-6506972ecb37

证书转换

1
2
3
4
5
6
7
8
// crt -> cer
openssl x509 -in 2023.jin.com.crt -out 2023.jin.com.cer -outform der
openssl s_client -connect pre-service.jfzfund.com:443 </dev/null 2>/dev/null | openssl x509 -outform DER > myWebsite.cer

// cer -> pem
openssl x509 -in dnufzfj0.kecezfj -pubkey -noout > dnufzfj0.pem
// 公钥文件
openssl x509 -inform der -in dnufzfj0.cer -out certificate.pem

通过 chrome 也可以导出.cer 格式证书

通过linux进行文件过滤

先通过 find 命令查找文件,然后对文件中内容,按照关键字进行过滤

1
find /mnt/pc-html/ -maxdepth 1 -name 'vpcspider01_data_config_*' -exec grep -Eonl 'c-.html' {} \;