前言:
2019 手机直播卖货火了起来,手机扫描文件,人脸识别等好多技术也越来越火。方便人们的技术以后会涉及生活的更多地方。因此,这种技术以后是种趋势,会需要很多研发人才。作为安卓开发者,选择音视频开发方向或者NDK是个不错的选择。而这个方向学习 c/c++ 是必须的,这里我们先学习c语言。
c语言入门
1.C语言的编译过程:
- 预处理 预处理阶段主要处理include和define等。它把#include包含进来的.h 文件插入到#include所在的位置,把源程序中使用到的用#define定义的宏用实际的字符串代替
- 编译 编译阶段,编译器检查代码的规范性、语法错误等,检查无误后,编译器把代码翻译成汇编语言
- 汇编 汇编阶段把 .s文件翻译成二进制机器指令文件.o,这个阶段接收.c, .i, .s的文件都没有问题
- 链接 链接阶段,链接的是其余的函数库,比如我们自己编写的c/c++文件中用到了三方的函数库,在连接阶段就需要连接三方函数库,如果连接不到就会报错
可以通过以下命令进行操作分布实现 :
// 将导入所有的.h文件(包括.h中引用的文件)
// #define定义的宏用实际的字符串代替
gcc -E main.c -o main.i
//检查代码的正确性
//编译成汇编语言
gcc -s main.i -o main.s
//将汇编语言翻译成二进制机器码
gcc -c main.s -o main.o
// 添加 我们引用的所有 静态库(.a)
gcc main.o -o main.exe
2.数据类型
整数类型
上表是不同整数类型所占的存储大小和取值范围。这里需要我们注意的是:不同的编译器下,数据存储的大小可能不同。
浮点型
以上就是整数和浮点数的存储大小和他们的精度,我们可以通过代码 sizeof 查看不同类型的存储大小。
//char
printf("char 的存储大小:%d\n", sizeof(char));
printf("unsigned char 的存储大小:%d\n", sizeof(unsigned char));
printf("signed char 的存储大小:%d\n", sizeof(signed char));
//int
printf("int 的存储大小:%d\n", sizeof(int));
printf("unsigned int 的存储大小:%d\n", sizeof(unsigned int));
printf("signed int 的存储大小:%d\n", sizeof(signed int));
//short
printf("short 的存储大小:%d\n", sizeof(short));
printf("unsigned short 的存储大小:%d\n", sizeof(unsigned short));
printf("signed short 的存储大小:%d\n", sizeof(signed short));
//long (这里还有个 long long)
printf("long 的存储大小:%d\n", sizeof(long));
printf("unsigned long 的存储大小:%d\n", sizeof(unsigned long));
printf("signed long 的存储大小:%d\n", sizeof(signed long));
printf("long long 的存储大小:%d\n", sizeof(long long));
//double
printf("double 的存储大小:%d\n", sizeof(double));
printf("double 最大值:%e\n", DBL_MAX); // #include <float.h>
printf("double 最小值:%e\n", DBL_MIN);
printf("double 精度:%e\n", DBL_DIG);
//float
printf("float 的存储大小:%d\n", sizeof(float));
printf("float 最大值:%e\n", FLT_MAX);
printf("float 最小值:%e\n", FLT_MIN);
printf("float 精度:%e\n", FLT_DIG);
//long double
printf("long double 的存储大小:%d\n", sizeof(long double));
printf("long double 最大值:%e\n", LDBL_MAX);
printf("long double 最小值:%e\n", LDBL_MIN);
printf("long double 精度:%e\n", LDBL_DIG);
在这里,我们需要解释一下为什么char的取值范围是~128-127。首先,在计算机中表示数字的规则:所有的数字由该数的补码表示。那什么是补码,原码和反码又是什么?
正数的 原码,反码,补码都一样,即该数的二进制数;
负数的 原码:该数的二进制数;
反码:用该数的原码求反码,符号位不变,其他位求反。即 1变0 0变1;
补码: 用该数的反码末位+1;
所以:char 1字节=8位,正数补码就是从 0000 0000开始到 0111 1111 即:0-127; 负数补码是从1111 1111到 1000 0000 即 -128 ~ -1。(-128 没有正码 反码)
那为什么使用补码表示数呢?符号位可以和其他位统一处理,减法可以按照加法计算。
格式占位符:
3.指针:
指针:一种特殊的变量,它保存着另一个变量的内存地址。我们可以通过它来直接操作内存数据。 最为特殊的变量,指针在使用之前需要先声明。
type * name=NULL;
type: 可以是任意的c语言数据类型;
name:是指针变量的名字; 例如: ptr_i;
NULL: 我们在声明指针时,最好赋值为NULL。不然的话该指针在赋值之前会默认一个随机的地址,直接操作对系统不友好;
4.指针的使用:
为了更加全面了解指针,我们看一下以下代码:
// 声明指针时,赋值位NULL是一个良好的习惯
int *ptr_i = NULL;
int *ptr_i1;
//打印指针的初始值-----0000000000000000
printf("prt_i 的NULL地址:%p\n", ptr_i);
//ptr_i1每次的指向发生变化 ---- 随机的值
printf("prt_i1 的随机地址:%p\n", ptr_i1);
//声明变量
int i = 10;
//将变量i的地址赋值给 ptr_i指针
ptr_i = &i;
// &加变量名,该变量的内存地址
printf("i 的内存地址:%p\n", &i);
// prt_i 的值(即 它指向的地址)
printf("ptr_i 指向的地址:%p\n", ptr_i);
// ptr_i 自身的地址ang
printf("ptr_i 自身的地址:%p\n", &ptr_i);
// ptr_i 指向地址的值
printf("ptr_i 指向的变量的值 %d\n", *ptr_i);
ptr_i = NULL;//最终释放指针,不然会变成 悬空指针和野指针一样危险
打印出来的值:
prt_i 的NULL地址:0000000000000000
prt_i1 的随机地址:0000000000000010
i 的内存地址:000000000061FE0C
ptr_i 指向的地址:000000000061FE0C
ptr_i 自身的地址:000000000061FE10
ptr_i 指向的变量的值 10
注意:使用完指针,置空指针; ptr_i = NULL;//最终将指针置空,不然会变成 悬空指针和野指针一样危险;
& 用于取一个变量的地址;
* 是一个间接寻址符 用于访问指针所指向的地址的值;
5.指针的大小:
指针的大小:32位的系统,指针占4个字节;64位的系统指针占8个字节。指针的大小和指针类型无关,与系统有关。
printf("char 指针的大小:%d\n", sizeof(char *));
printf("int 指针的大小:%d\n", sizeof(int *));
printf("void 指针的大小:%d\n", sizeof(void *));
printf("long long 指针的大小:%d\n", sizeof(long long *));
char 指针的大小:8
int 指针的大小:8
void 指针的大小:8
long long 指针的大小:8
6.通用指针:
任意一种类型的指针都可以转化成通用指针,指向的地址不会发生改变,但是指针类型丢失。
int *prptr_i = NULL;
void *ptr_v = NULL;
//将int类型的指针,赋值给void*通用指针
ptr_v = ptr_i;
printf("void指向的值: %d\n", *(int *) ptr_v);
printf("void*的自身地址: %p\n", &ptr_v);
printf("void存储的地址: %p\n", ptr_v);
printf("ptr_i 指向的地址:%p\n", ptr_i);
prptr_i = NULL;
ptr_v = NULL;
打印结果:void指向的值: 10
void*的自身地址: 000000000061FE00
void存储的地址 000000000061FE0C
ptr_i 指向的地址:000000000061FE0C
ptr_v 和 ptr_i 是两个指针,指向了同一地址。但是,ptr_v没有了指针类型。 在使用指向地址的值时,必须强转指针类型。
7.指针的算术运算:
上面我们可以看到无论什么类型的指针,它的指针大小在一样的系统下都是一样的。那么指针类型有什么用呢?
在C语言中,我们可以对指针进行算术运算(+ ,-,++,--,+=,-=),也就是指针可以进行加减运算。具体规则如下:
1.指针的每一次递增,它其实会指向下一个元素的存储单元。
2.指针的每一次递减,它都会指向前一个元素的存储单元。
3.指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节。
int n = 1000;
int *ptr_n = &n;
printf("ptr_n = %p\n", ptr_n);
printf("ptr_n+1 = %p\n", ptr_n + 1);
printf("ptr_n+2 = %p\n", ptr_n + 2);
short m = 1000;
short *ptr_m = &m;
printf("ptr_m = %p\n", ptr_m);
printf("ptr_m+1 = %p\n", ptr_m + 1);
printf("ptr_m+2 = %p\n", ptr_m + 2);
打印结果:ptr_n = 000000000061FDEC
ptr_n+1 = 000000000061FDF0
ptr_n+2 = 000000000061FDF4
ptr_m = 000000000061FDEA
ptr_m+1 = 000000000061FDEC
ptr_m+2 = 000000000061FDEE
可以发现 int类型的指针每次增加的步长是4,short的每次是2.正好是该类型的存储大小。
8.指针与一维数组:
c语言中数组声明和java中没有多大区别。
1. int array[]={1,2,3,4,5}; array 其实就是一个指针,指向数组座标为0的元素的地址。
int array[] = {1, 2, 3, 4, 5};
int *ptr_array = array; //这里不需要& 即可赋值,因为array就是指针
printf("ptr_array=%p\n", ptr_array);
printf("array=%p\n", array);
printf("&array[0]=%p\n", &array[0]);
打印结果:ptr_array=000000000061FDC0
array=000000000061FDC0
&array[0]=000000000061FDC0
2.获取数组中元素的值的方法
例如:获取座标为2 的数组元素的方法: array[2] ; *(array+2) ; ptr_array[2] ; *(ptr_array+2)
printf("*(ptr_array+2) = %d\n", *(ptr_array + 2));
printf("ptr_array[2] = %d\n", ptr_array[2]);
printf("*(array+2) = %d\n", *(array + 2));
printf("array[2] = %d\n", array[2]);
9.指针数组与数组指针:
printf("array+1 = %p\n", array + 1);
printf("&array+1 = %p\n", &array + 1);
先看上面这两行代码,它两的打印结果却不一样。
array+1 = 000000000061FDC4
&array+1 = 000000000061FDD4
array +1 :增加了4字节,&array+1增加了20字节。
这是因为 array 是指向第一个元素的指针;
而 &array则为:int (* ptr_array)[5] = &array; 这是数组指针,增加一位,指向的地址增加的是数组的大小。
指示点:
1. 数组指针的模板: int (* ptr_array) [ number];
2. 指针数组: int * ptr_array [number] ; 存储指针的数组。
3. 符号优先级:()> [ ] > *