Android | 写给 Android 应用开发者的 C 语言快速入门指北
据非官方不负责任的统计,有百分之八十的 Android 应用开发者,不会或者很少有机会写 C/C++。
如果你是一个有追求的程序员,还是把 C 语言捡起来吧。
源码
本文所有涉及的所有源码:https://github.com/gavinliu/Study-C-CPP
数据类型
C 语言是一个有类型的语言,使用变量必须定义确定类型。
- 整数:char, short, int, long, long long, bool
- 浮点数:float, double, long double
- 指针
- 自定义类型
数据类型的不同
在 64 位机器上:
数据类型 | 内存长度 | 格式化 |
---|---|---|
char | 1 Byte | %d |
short | 2 Byte | %d |
int | 4 Byte | %d |
long | 8 Byte | %ld |
float | 4 Byte | %f |
double | 8 Byte | %lf |
整数和浮点数 在内存中的形式都是以二进制的形式存在的,但是整数是真实的二进制数字 可以直接拿来运算,而浮点数是一种编码方式,不能直接运算。通常来说 CPU 上专门有一个硬件来处理浮点数运算。
sizeof
这个方法可以返回某个类型或者某个变量在内存中所占据的字节数
1 | sizeof(int); |
整数
int 的特殊意义
int 和 long 的长度取决于编译器和 CPU,通常的意义是表示 “一个字” 的长度。
什么叫一个字?
CPU 和内存读取数据模型:
CPU [register] <—-总线—-> RAM
CPU 中的寄存器通过总线和内存进行数据读写,现在的 CPU 的寄存器通常这个大小是 32bit 或者 64bit,这个大小就是一个字的长度,通常就代表 int 的长度。
内部的表达
在计算机中任何数据都是二进制数据,这个数据是什么取决与我们怎么看它。
1 个字节可以表达的数范围:
00000000 ~ 11111111 (0~255)
因为 char 刚好就是一个字节,我们就拿 char 来实验一下:
因为一个字节,只能表达 256 个数字,那么 char 的表示数的范围是 (0 ~ 255) 还是可以表示负数呢?
1 | char c = -1; |
1 | c=-1 |
测试代码可以看出 char 是可以表示负数的。
那么负数怎么表示呢?对应的二进制又是怎样的呢?
1 –> 00000001
0 –> 00000000
-1 –> ?
我们这样这样想 -1 + 1 = 0,那么:
1 | ???????? |
这样看就很明显了:
1 | 11111111 |
100000000
由于 超出了 1 个字节的范围,所以溢出了就变成了 00000000
。
11111111 和 00000001 是什么关系呢?
原码:00000001
反码:11111110
补码:11111111
所以虽然内存中的数据都是 11111111
,但是当被当成原码看的时候是 255
,当成补码看的时候就是 -1
。
signed & unsigned
signed, unsigned 数据类型的修饰符,只能修饰 整数类型的数据,表示是否带符号,也就是不用补码来表示负数了。
8 进制 16 进制
1 | int i = 012; // 八进制 |
浮点数
精度误差
1 | float f = 1.123f; |
1 | 不相等 sum=3.2459998131, sum=3.246000 |
浮点数是不准确,有效位数是有限的,所以在处理钱的问题上,不要使用小数点来表示角分,而是转换成最小单位来计算,比如:
1.23 元 = 123 分,这样转换成整数计算就不会又误差了。
指针
指针是什么? 指针就是一个内存地址,代表的就是一块内存空间。
1 | // 定义一个 int类型的变量 i 值为5 |
指针占多少字节
1 | int i =3; |
数组
数组变量是特殊的指针
- 无需用 & 取地址
1 | int a[10]; int *p = a; |
- 但是数组的单元表达的是变量,所以是需要用 & 取地址
1 | *a == &a[0] |
[]
可以对数组做,也可以对指针做。
1 | p[0]; a[0]; |
*
也可以对数组做
1 | *a = 0; |
- 数组变量是一个 const 的指针,所以不能被再次赋值,只能初始化。
1 | int b[] = a; |
指针运算
1 | // 指针的运算和数组都是紧密关联的 |
1 | printf("dist=%c\n", &arr[0] - &arr[2]); // dist=2 |
指针类型转换
1 | int* p = &i; |
函数指针
1 | void print(char const * str) { |
指针的运用
- 需要传入较大的数据时用作参数:传数组
- 对数组的操作
- 函数返回值不止一个结果的时候
- 需要用函数来修改不止一个变量:swap
- 动态申请内存
- 函数指针相当于 java 的回调
动态内存分配
1 |
|
字符串
声明字符串的三种方式
1 | char c[] = {'H', 'e', 'l', 'l', 'o', '\0'}; |
字符串数组
1 | char s[][] = {"Hello", "Wrold"}; |
字符串操作
1 |
在标准函数库 string.h
里面有关于 string 处理相关的函数库。
具体方法查找:http://www.runoob.com/cprogramming/c-standard-library-string-h.html
结构类型
枚举
1 | enum Type { |
结构体
1 | struct Position { |
结构体的内存大小为一般来说所有成员的大小之和,但是也会存在内存对齐的情况:
- 结构体变量中成员的偏移量必须是成员大小的整数倍(0 被认为是任何数的整数倍)
- 结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。
结构体初始化
1 | struct Position p = {1, 2}; |
结构体指针
1 | struct Position* p = &q; |
C 的 ->
操作符,其实就是指针取值的简写。
结构体运算
1 | p = (struct Position) {1, 2}; // 相当于 p.x = 1; p.y = 2; |
联合体
1 | union Value { |
联合体的大小,会存在内存对齐的情况,其内存大小等于成员中最大的那个属性的长度,并且因为联合体内的成员是共用内存的,所以只会安全的保存一个值。
1 | 1, 0.000000, , 0.000000 |
别名
1 | typedef int Age; |
1 | Age a = 1; |
1 | 1 |
给结构体取别名,可以让结构体的使用更加接近于 Java。
宏定义
1 |
下面这个是 标准 C header 的宏定义,为了解决头文件的导入死循环
1 |
|
在 C99 之前,C 语言没有 const
关键字,所以定义常量通常也是用宏定义
1 |
编译器处理宏定义其实就是最原始的文本替换。
1 | double x = 2 * PI; |
==>
1 | double x = 2 * 3.14159; |
带参数的宏
1 |
|