C++ Memorandum
作为一名对自身职业充满热爱的Android工程师,C/C++的重要性不言而喻,以图镇楼,提醒自己~
android_structure.png
1、基本路线
1.1、数据类型
1)一些基本类型可以使用一个或多个类型修饰符进行修饰:
- signed
- unsigned
- short
- long
2)typedef 声明
您可以使用 typedef 为一个已有的类型取一个新的名字。下面是使用 typedef 定义一个新类型的语法:
typedef type newname;
例如,下面的语句会告诉编译器,feet 是 int 的另一个名称:
typedef int feet;
feet distance;
上面的声明是完全合法的,它创建了一个整型变量 distance:
1.2、存储类
1) auto
-
声明变量时根据初始化表达式自动推断该变量的类型、
-
声明函数时函数返回值的占位符。
2) static
- 当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内;
3)extern
- 放在函数或者变量前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义,extern只是声明,而不是定义,也就是说extern并不分配空间。
- 与“C”连用,作为连接指定。
1.3、循环
1)for
- 一般情况下,C++ 程序员偏向于使用 for(;;) 结构来表示一个无限循环。
1.4、函数
1)函数声明
在函数声明中,参数的名称并不重要,只有参数的类型是必需的,比如如下两种声明等价:
int max(int num1, int num2);
int max(int, int);
2)函数参数
当调用函数时,有三种向函数传递参数的方式:
调用类型 | 描述 |
---|---|
传值调用 | 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。 |
指针调用 | 该方法把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
引用调用 | 该方法把参数的引用复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
3)参数的默认值
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 返回类型 (*新类型)(参数表)
typedef char (*PTRFUN)(int);
PTRFUN pFun;
char glFun(int a){ return;}
void main()
{
pFun = glFun;
(*pFun)(2);
}
#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)多维数组
#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)初始化数组
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 风格字符串
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;如果 s1<s2 则返回值小于 0;如果 s1>s2 则返回值大于 0。 |
5 | strchr(s1, ch): 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
6 | strstr(s1, s2): 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
1.7、指针
1)空指针
-
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯;
#include <iostream> using namespace std; int main () { int *ptr = NULL; cout << "ptr 的值是 " << ptr ; return 0; }
-
检查一个空指针,您可以使用 if 语句,如下所示:
if(ptr) /* 如果 ptr 非空,则完成 */ if(!ptr) /* 如果 ptr 为空,则完成 */
2)指针的算术运算
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 是一个指向数组开头的常量,不能作为左值。
int main () { int var[MAX] = {10, 100, 200}; for (int i = 0; i < MAX; i++) { *var = i; // 这是正确的语法 var++; // 这是不正确的 } return 0; }
4)指针数组
5)指向指针的指针
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)实例
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、C++类型转换之reinterpret_cast
TODO:https://zhuanlan.zhihu.com/p/33040213
2.3、整型数字的表示
10 //普通的整型数字类型
10u //无符号数
10L //长整型数
012 // 八进制数
0xc // 十六进制数
2.4、初始化列表
与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段,示例:
struct foo
{
string name ;
int id ;
foo(string s, int i):name(s), id(i){} ; // 初始化列表
};
初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。使用初始化列表主要是基于性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,因为使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。
除了性能问题之外,有些时场合初始化列表是不可或缺的,以下几种情况时必须使用初始化列表
- 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
- 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
- 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。
另外需要注意一下,成员变量的初始化顺序问题。
3、零零散散
- 在C++中,struct和class的唯一区别是默认的访问性不同;
- 意识到转义字符在字符串输出中的作用,比如如下代码:
int main(int argc, char *argv[]) {
std::cout << "Who goes with F\145rgus?\012" << std::endl;
}
最后输出的结果就是Who goes with Fergus?
- 对于int类型来讲,按照C++规定,全局变量的默认值是0,局部变量的默认值是一个奇异值;