据非官方不负责任的统计,有百分之八十的Android应用开发者,不会或者很少有机会写C/C++。
如果你是一个有追求的程序员,还是把C语言捡起来吧。
源码
本文所有涉及的所有源码:github.com/gavinliu/St…
数据类型
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
这个方法可以返回某个类型或者某个变量在内存中所占据的字节数
sizeof(int);
int a = 0;
sizeof(a);
整数
int 的特殊意义
int 和 long 的长度取决于编译器和CPU,通常的意义是表示 “一个字” 的长度。
什么叫一个字?
CPU和内存读取数据模型:
CPU [register] <—-总线—-> RAM
CPU中的寄存器通过总线和内存进行数据读写,现在的CPU的寄存器通常这个大小是 32bit 或者 64bit,这个大小就是一个字的长度,通常就代表 int 的长度。
内部的表达
在计算机中任何数据都是二进制数据,这个数据是什么取决与我们怎么看它。
1个字节可以表达的数范围:
00000000 ~ 11111111 (0~255)
因为char刚好就是一个字节,我们就拿char来实验一下:
因为一个字节,只能表达 256 个数字,那么 char 的表示数的范围是 (0 ~ 255) 还是可以表示负数呢?
char c = -1;
printf(c="%d\n", c);
c=-1
测试代码可以看出 char 是可以表示负数的。
那么负数怎么表示呢?对应的二进制又是怎样的呢?
1 –> 00000001
0 –> 00000000
-1 –> ?
我们这样这样想 -1 + 1 = 0,那么:
????????
+ 00000001
= 00000000
这样看就很明显了:
11111111
+ 00000001
=100000000
= 00000000
100000000
由于 超出了 1个字节的范围,所以溢出了就变成了 00000000
。
11111111 和 00000001 是什么关系呢?
原码:00000001
反码:11111110
补码:11111111
所以虽然内存中的数据都是 11111111
,但是当被当成原码看的时候是 255
,当成补码看的时候就是 -1
。
signed & unsigned
signed, unsigned 数据类型的修饰符,只能修饰 整数类型的数据,表示是否带符号,也就是不用补码来表示负数了。
8进制 16进制
int i = 012; // 八进制
int j = 0x12; // 十六机制
printf("i=0%o,j=%x\n", i, j);
浮点数
精度误差
float f = 1.123f;
float ff = 2.123f;
float sum = f + ff;
if (sum == 3.246f) {
printf("相等\n");
} else {
printf("不相等 sum=%.10f, sum=%f\n", sum, sum);
}
不相等 sum=3.2459998131, sum=3.246000
浮点数是不准确,有效位数是有限的,所以在处理钱的问题上,不要使用小数点来表示角分,而是转换成最小单位来计算,比如:
1.23元 = 123分,这样转换成整数计算就不会又误差了。
指针
指针是什么? 指针就是一个内存地址,代表的就是一块内存空间。
// 定义一个 int类型的变量 i 值为5
int i = 5;
// &i 中的 &为取地址符,可以得到 i 的地址,%x 为十六进制输出
printf("i的地址 %p\n",&i);
// 指针变量 定义一个int* 类型的变量p
int* p;
// 把 i 的地址给 p,现在 p 就存放的 i 的地址
p = &i;
printf("i=%d\n",i);
// *p --> 当指针变量前面有*的时候,表示取这个指针变量所存放的地址里面对应的数据
printf("*p的值%d\n",*p);
指针占多少字节
int i =3;
double d = 3.141692;
float f = 3.1423;
char c ='B';
int* ip = &i;
double* dp = &d;
float* fp = &f;
char* cp = &c;
printf("int 类型指针变量的长度为 %d\n",sizeof(ip)); // --> 4
printf("double 类型指针变量的长度为 %d\n",sizeof(dp)); // --> 4
printf("float 类型指针变量的长度为 %d\n",sizeof(fp)); // --> 4
printf("char 类型指针变量的长度为 %d\n",sizeof(cp)); // --> 4
数组
数组变量是特殊的指针
-
无需用 & 取地址
int a[10]; int *p = a;
-
但是数组的单元表达的是变量,所以是需要用 & 取地址
*a == &a[0]
-
[]
可以对数组做,也可以对指针做。p[0]; a[0];
-
*
也可以对数组做*a = 0;
-
数组变量是一个 const 的指针,所以不能被再次赋值,只能初始化。
int b[] = a; // int b[] === int * const b
指针运算
// 指针的运算和数组都是紧密关联的
char* pChar = &arr[2];
printf("pChar的内存地址:%#X,对于的值为:%c\n", pChar, *(pChar));
printf("pChar + 1 的内存地址:%#X,对于的值为:%c\n", pChar + 1, *(pChar + 1));
// pChar的内存地址:0X28FF22,对于的值为:c
// pChar + 1 的内存地址:0X28FF23,对于的值为:d
// 指针的运算 按照 约定好的数据类型, 偏移相对应的内存空间的大小 !
printf("dist=%c\n", &arr[0] - &arr[2]); // dist=2
// 指针减法 返回的值是距离,并不是返回整数相减的结果
指针类型转换
int* p = &i;
void* q = (void*) p;
函数指针
void print(char const * str) {
printf("%s\n", str);
}
void sayHi(void (* p)(char const * str)) {
p("hi2");
}
sayHi(print);
指针的运用
- 需要传入较大的数据时用作参数:传数组
- 对数组的操作
- 函数返回值不止一个结果的时候
- 需要用函数来修改不止一个变量:swap
- 动态申请内存
- 函数指针相当于java的回调
动态内存分配
#include
// 申请 n个int 大小内存,失败返回 0
int* p = (int*) malloc(n * sizeof(int));
// 回收
free(p);;
字符串
声明字符串的三种方式
char c[] = {'H', 'e', 'l', 'l', 'o', '\0'};
printf("%s\n", c);
char c2[6] = {'H', 'e', 'l', 'l', 'o'};
printf("%s\n", c2);
char* str = "Hello World!";
printf("%s\n", str);
字符串数组
char s[][] = {"Hello", "Wrold"};
char *ss[] = {"Hello", "Wrold"};
字符串操作
#include <string.h>
在标准函数库 string.h
里面有关于 string 处理相关的函数库。
具体方法查找:www.runoob.com/cprogrammin…
结构类型
枚举
enum Type {
Open,
Close
};
enum Type t = Open;
结构体
struct Position {
int x;
int y;
};
结构体的内存大小为所有成员的大小之和
结构体初始化
struct Position p = {1, 2};
struct Position q;
q.x = 2;
q.y = 2;
结构体指针
struct Position* p = &q;
(*p).x = 1;
p->y = 2;
C的 ->
操作符,其实就是指针取值的简写。
结构体运算
p = (struct Position) {1, 2}; // 相当于 p.x = 1; p.y = 2;
p1 = p2 // 相当于 p1.x = p2.x; p1.y = p2.y
联合体
union Value {
int x;
float y;
char z;
double q;
};
union Value v;
v.x = 1;
printf("%d, %f, %c, %f\n", v.x, v.y, v.z, v.q);
v.z = 'A';
printf("%d, %f, %c, %f\n", v.x, v.y, v.z, v.q);
v.y = 0.1;
printf("%d, %f, %c, %f\n", v.x, v.y, v.z, v.q);
v.q = 0.2;
printf("%d, %f, %c, %f\n", v.x, v.y, v.z, v.q);
printf("%zu\n", sizeof(union Value));
联合体的大小,会存在内存对齐的情况,其内存大小等于成员中最大的那个属性的长度,并且因为联合体内的成员是共用内存的,所以只会安全的保存一个值。
1, 0.000000, , 0.000000
65, 0.000000, A, 0.000000
-1717986918, 0.100000, �, 0.100000
-1717986918, 0.200000, �, 0.200000
8
别名
ttypedef int Age;
typedef int* AgeP;
struct Man {
char* name;
};
typedef struct Man Student, *StudentP;
Age a = 1;
printf("%d\n", a);
AgeP p = &a;
printf("%d\n", *p);
Student student;
student.name = "Jack";
printf("%s\n", student.name);
StudentP sp = &student;
printf("%s\n", sp->name);
1
1
Jack
Jack
给结构体取别名,可以让结构体的使用更加接近于Java。
宏定义
#define value
下面这个是 标准 C header 的宏定义,为了解决头文件的导入死循环
#ifndef __X__HEADER__
#define __X__HEADER__
void hello();
#endif
在C99之前,C语言没有 const
关键字,所以定义常量通常也是用宏定义
#define PI 3.14159
编译器处理宏定义其实就是最原始的文本替换。
double x = 2 * PI;
==>
double x = 2 * 3.14159;
带参数的宏
#define cube(x) ((x)*(x)*(x))
printf("%d\n", cube(2)); // 8