C++ Memorandum

作为一名对自身职业充满热爱的Android工程师,C/C++的重要性不言而喻,以图镇楼,提醒自己~

android_structure

1、基本路线

1.1、数据类型

1)一些基本类型可以使用一个或多个类型修饰符进行修饰:

  • signed
  • unsigned
  • short
  • long

2)typedef 声明

您可以使用 typedef 为一个已有的类型取一个新的名字。下面是使用 typedef 定义一个新类型的语法:

1
typedef type newname;

例如,下面的语句会告诉编译器,feet 是 int 的另一个名称:

1
2
3
typedef int feet;

feet distance;

上面的声明是完全合法的,它创建了一个整型变量 distance:

1.2、存储类

1) auto

  • 声明变量时根据初始化表达式自动推断该变量的类型、

  • 声明函数时函数返回值的占位符。

2) static

  • 当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内;

3)extern

  • extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候;

1.3、循环

1)for

  • 一般情况下,C++ 程序员偏向于使用 for(;;) 结构来表示一个无限循环。

1.4、函数

1)函数声明

在函数声明中,参数的名称并不重要,只有参数的类型是必需的,比如如下两种声明等价:

1
int max(int num1, int num2);
1
int max(int, int);
2)函数参数

当调用函数时,有三种向函数传递参数的方式:

调用类型 描述
传值调用 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。
指针调用 该方法把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
引用调用 该方法把参数的引用复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
3)参数的默认值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int sum(int a, int b=20){
int result;
result = a + b;
return (result);
}

int main (){
// 局部变量声明
int a = 100;
int result;
// 再次调用函数
result = sum(a);
cout << "Total value is :" << result << endl;
return 0;
}
4)typedef函数指针

形式:typedef 返回类型 (*新类型)(参数表)

1
2
3
4
5
6
7
8
typedef char (*PTRFUN)(int); 
PTRFUN pFun;
char glFun(int a){ return;}
void main()
{
pFun = glFun;
(*pFun)(2);
}
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include <stdio.h>
#include <assert.h>

typedef int (*FP_CALC)(int,int);//定义一个函数指针类型

int add(int a, int b)
{
return a + b;
}

int sub(int a, int b)
{
return a - b;
}

int mul(int a, int b)
{
return a * b;
}

int div(int a, int b)
{
return b ? a/b : -1;
}

//定义一个函数,参数为op,返回一个指针,该指针类型为拥有两个int参数、
//返回类型为int的函数指针。它的作用是根据操作符返回相应函数的地址
FP_CALC calc_func(char op)
{
switch( op )
{
case '+':
return add;
case '-':
return sub;
case '*':
return mul;
case '/':
return div;
default:
return NULL;
}
return NULL;
}

//s_calc_func为函数,它的参数是 op,
//返回值为一个拥有两个int参数、返回类型为int的函数指针
int (*s_calc_func(char op)) (int , int)
{
return calc_func(op);
}

//最终用户直接调用的函数,该函数接收两个int整数,
//和一个算术运算符,返回两数的运算结果
int calc(int a, int b, char op)
{
FP_CALC fp = calc_func(op);
int (*s_fp)(int,int) = s_calc_func(op);//用于测试

assert(fp == s_fp);// 可以断言这两个是相等的

if(fp)
return fp(a,b);
else
return -1;
}

void main()
{
int a = 100, b = 20;

printf("calc(%d, %d, %c) = %d\n", a, b, '+', calc(a, b, '+'));
printf("calc(%d, %d, %c) = %d\n", a, b, '-', calc(a, b, '-'));
printf("calc(%d, %d, %c) = %d\n", a, b, '*', calc(a, b, '*'));
printf("calc(%d, %d, %c) = %d\n", a, b, '/', calc(a, b, '/'));
}
5)C++ 11 Lambda表达式

1.5、数组

1)多维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using namespace std;

int main ()
{
// 一个带有 5 行 2 列的数组
int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};

// 输出数组中每个元素的值
for ( int i = 0; i < 5; i++ )
for ( int j = 0; j < 2; j++ )
{
cout << "a[" << i << "][" << j << "]: ";
cout << a[i][j]<< endl;
}

return 0;
}

2)初始化数组

1
2
3
4
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

//如果省略掉了数组的大小,数组的大小则为初始化时元素的个数。:
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

1.6、字符串

C++ 提供了以下两种类型的字符串表示形式:

  • C 风格字符串
  • C++ 引入的 string 类类型

1)C 风格字符串

1
2
3
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char greeting[] = "Hello";
const char *greeting = "Hello";

C++ 中有大量的函数用来操作以 null 结尾的字符串:

序号 函数 & 目的
1 strcpy(s1, s2):复制字符串 s2 到字符串 s1。
2 strcat(s1, s2): 连接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1): 返回字符串 s1 的长度。
4 strcmp(s1, s2): 如果 s1 和 s2 是相同的,则返回 0;如果 s1s2 则返回值大于 0。
5 strchr(s1, ch): 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2): 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

1.7、指针

1)空指针

  • 在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <iostream>

    using namespace std;

    int main ()
    {
    int *ptr = NULL;

    cout << "ptr 的值是 " << ptr ;

    return 0;
    }
  • 检查一个空指针,您可以使用 if 语句,如下所示:

    1
    2
    if(ptr)     /* 如果 ptr 非空,则完成 */
    if(!ptr) /* 如果 ptr 为空,则完成 */

2)指针的算术运算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int main ()
{
int var[MAX] = {10, 100, 200};
int *ptr;

// 指针中的数组地址
ptr = var;
for (int i = 0; i < MAX; i++)
{
cout << "Address of var[" << i << "] = ";
cout << ptr << endl;

cout << "Value of var[" << i << "] = ";
cout << *ptr << endl;

// 移动到下一个位置
ptr++;
}
return 0;
}

3)指针 VS 数组

  • 指针和数组在很多情况下是可以互换的;

  • 修改 var 的值是非法的。这是因为 var 是一个指向数组开头的常量,不能作为左值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    int main ()
    {
    int var[MAX] = {10, 100, 200};

    for (int i = 0; i < MAX; i++)
    {
    *var = i; // 这是正确的语法
    var++; // 这是不正确的
    }
    return 0;
    }

4)指针数组

5)指向指针的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main ()
{
int var;
int *ptr;
int **pptr;

var = 3000;

// 获取 var 的地址
ptr = &var;

// 使用运算符 & 获取 ptr 的地址
pptr = &ptr;

// 使用 pptr 获取值
cout << "var 值为 :" << var << endl;
cout << "*ptr 值为:" << *ptr << endl;
cout << "**pptr 值为:" << **pptr << endl;

return 0;
}

1.8、引用

1)引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字;

2)引用 vs 指针

引用很容易与指针混淆,它们之间有三个主要的不同:

  • 不存在空引用。引用必须连接到一块合法的内存。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。

3)实例

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
int main ()
{
// 声明简单的变量
int i;
double d;

// 声明引用变量
int& r = i;
double& s = d;

i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;

d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;

return 0;
}

// 当上面的代码被编译和执行时,它会产生下列结果:
Value of i : 5
Value of i reference : 5
Value of d : 11.7
Value of d reference : 11.7

1.9、类

1)拷贝构造函数
2)构造函数初始化列表

构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。

TODO:https://www.runoob.com/w3cnote/cpp-construct-function-initial-list.html

2、知识积累

2.1、静态库和动态库

  • 在Linux中静态库是以 .a 为后缀的文件,共享库是以 .so为后缀的文件。
  • 静态库和动态库的区别:

    • 当程序与静态库连接时,源文件中所有引用的库函数所对应的目标文件和源文件的目标文件会一起链接起来生成可执行文件。这就会导致最终生成的可执行代码量相对变多,相当于编译器将代码补充完整了,这样运行起来相对就快些。缺点: 占用磁盘和内存空间. 静态库会被添加到和它连接的每个程序中, 而且这些程序运行时, 都会被加载到内存中. 无形中又多消耗了更多的内存空间.

    • 当程序与共享库连接时,只包含它需要的函数的引用表,而不是所有的函数代码,只有在程序执行时, 那些需要的函数代码才被拷贝到内存中。这样就使可执行文件比较小, 节省磁盘空间,更进一步,操作系统使用虚拟内存,使得一份共享库驻留在内存中被多个程序使用,也同时节约了内存。缺点:不过由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些。

  • 一个程序编好后,有时需要做一些修改和优化,如果我们要修改的刚好是库函数的话,在接口不变的前提下,使用共享库的程序只需要将共享库重新编译就可以了(增量更新),而使用静态库的程序则需要将静态库重新编译好后,将程序再重新编译一遍(全量更新)。

2.2、std::lock_guard

TODO:https://www.jianshu.com/p/681f553fa4ab

2.3、std::make_unique和std::make_shared

2.4、C++类型转换之reinterpret_cast

TODO:https://zhuanlan.zhihu.com/p/33040213

3、参考