目录
8进制与16进制
0x00 计算机原理
01 缓冲区(buffer)与缓存(cache)
buffer是内存的一部分
cache是CPU,磁盘等的一部分
简单来说就是buffer偏重于写,而cache偏重于读。
02 一个程序运行时的内存空间
栈区(stack) | 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等 |
堆区(heap) | 一般由程序员分配释放(malloc), 若程序员不释放,程序结束时可能由OS回收 |
全局区(静态区)(static) | 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放 |
文字常量区 | 一般常量就是放在这里的。不能被修改。 程序结束后由系统释放 |
程序代码区 | 存放函数体的二进制代码 |
0x01 输入专题
01 scanf
强烈推荐这篇博客,一定要去看看:scanf函数读取缓冲区数据的问题
-
scanf(" ")
;如果双引号里面的空格可以跳过广义上的空格,等价于循环+getchar -
正则表达式
-
[]
内是匹配的字符。 -
^
表示求反集,当遇到非集合内的字符时立即终止输入。 -
%
表示选择,%*
表示跳过,其后一定要有新的%
语句,否则无法读入。 -
%
,%*
后面的是条件,比如%s
,s
是一个条件,表示任意字符。%3s
又多了一个条件:只拷贝3个字符。%[a-z]
的条件稍微严格一些,输入的东西不但是字符,还得是一个小写字母的字符。
,也就是跳过满足条件的字符, -
注:
%[a-b 0-9]
能够读入空格。单个字符也可以直接写在中括号里面
-
-
fgets(c, n, fp)
与gets
不同,第一、fgets
需要加最大输入长度n
这个参量, 表示加了\0
之后fgets
能读入的最大长度。因此需要在读入的最末尾主动赋值为\0
,不必管是否有\n
,这样能保证数据没有多余的\n
-
对正文文件的
fscanf
等函数大都和标准I/O下的函数用法一样。除了fgets
02 sscanf
本质上与scanf
相同,只不过将目标字符串当作stdin
同理还有sprintf
0x02 文件专题
01 fopen
-
fopen
必须要搭配文件指针使用,如fp = fopen( , )
而且得配合fscanf
等等,但是在关闭文件之可以同时对文件和标准输入输出进行操作。 -
文件打开一定要判断是否成功。如果不成功,可以使用
perror(const char *s)
它可将 上一个函数(一般是各个库函数) 发生错误的原因输出到标准设备(stderr)。参数s所指的字符串会先打印出,后面再加上错误原因字符串。
02 文本方式打开与二进制方式打开
i. 区别
数据怎么在磁盘上写不是由文件打开方式决定的,而是由写函数决定的。数据怎么从磁盘上读也不是由文件打开方式决定的,而是由读函数决定的。
上面说的数据怎么写是指:一种类型的变量是怎么存的?比如int 12
,可以直接存12的二进制码(4个字节),也可以存字符‘1’
,字符'2'
.
数据怎么读的是指:我要读一个int变量,是直接读sizeof(int)个字节,还是一个字符一个字符的读,直到读到的字符不是数字字符。
这对应了两种函数
ii. 如何读写
-
fread
以二进制的方式读入,fscanf
以正文方式读入。
fread
可以直接按几个字节为单位读,而fscanf
则是把文章解释为字符串然后再读。 -
fread
返回值是成功读入的以size为单位长度的项数
iii. 判断末尾
首先说明一点,ASCII表有256个字符。
EOF
是等于-1,但是 实际上文档末尾是没有EOF
这个玩意的。EOF
是读入函数的返回值。
在以文本方式打开的文件中,返回值为int
,不会与ASCII(char类型)冲突。故EOF有效。
而在以二进制打开的文件中,要想读字符,就必须得用 sizeof(char)
,这样不可避免地就会出现 EOF
与ASCII冲突。此时就得用feof来判断。
feof
:若为末尾返回非零,若还没到就返回0。 返回非零的触发条件和 fgets
等函数返回-1的条件相同
03 fseek 和 ftell
i ftell
原型如下
long ftell(FILE *stream);
它的返回值为long
类型,只有一个参数为文件指针
ii fseek
原型如下
int fseek (FILE * stream, long int offset, int origin );
- 它中间的那个参数是
long
类型,因此中间那个参数可以这样写:22L
,L
表示这个整数是long
类型。
需要注意的是,offset移动的量永远是字节数。 由于二进制文件和文本文件存储格式的区别 ,需要自己手动计算偏移量 - 第三个参数只有三个值
SEEK_SET
表示文件开头,SEEK_CUR
表示当前指针位置,SEEK_END
表示文件末尾 - 当offset是向文件尾方向偏移的时候,无论偏移量是否超出文件尾,fseek都是返回0,当偏移量超出文件尾的时候,文件指针是指向文件尾的。并不会返回偏移出错-1值。当offset是向文件头方向偏移的时候,如果offset没有超出文件头,fseek返回值为0.当offset超出文件头时,fseek返回出错-1值,文件指针不变还是处于原来的地址。
0x03 变量专题
01 extern
extern a
显式的说明了a
的存储空间是在程序的其他地方分配的,在文件中其他位置或者其他文件中寻找a
这个变量。
02 const
用const修饰的变量通常也叫常变量,因为这个变量有地址,有空间。只不过它的读写方式设定为了只读,这也就意味这只能通过赋初值的方式给它值。
const(运行时概念) | define(编译时概念) | |
---|---|---|
原理 | 常量声明 | 字符替换 |
谁来编译? | 编译器 | 预编译器 |
空间 | 需要分配空间 (C与C++中const变量内存分配问题详解)) |
不需要分配空间 |
安全检查 | 有类型区别,需要在编译阶段进行类型检查 | 没有数据类型的区别,没有类型安全检查 |
有无生命周期 | 有(当前函数) | 无(全局) |
(注:const在C语言中与在C++中的内存分配方式不同。此处讨论C语言的情况)
const修饰的全局变量,在常量区分配内存空间,不能通过变量地址来修改值;
const修饰的局部变量在栈区分配内存空间,可以通过变量地址来修改值
ii 与指针搭配
const的位置可以发生改变,但有可能含义就发生了变化
例如: const int * p
和 int const * p
等价,表示 *p
不能改,
而 int * const p
则表示 p
不能改。
如何记?
*
是右结合运算符,它若先和p
结合,那const
修饰的就是*p
,否则const
修饰的就是p
03 typedef
typedef基本用法
在一个定义 typedef <字符串>
中, <字符串>
中会出现一个未定义的类型名 Type_A
当你写:Type_A object;
的时候,它的含义就是:用 object
去替换 <字符串>
中的 Type_A
。
例如:
typedef int int_array[4];
int_array object;
实际上是:用 object
替换 字符串 "int int_array[4];"
中的 int_array
得到的结果: int object[4];
又例如:
typedef void (*PU)(int a, char b);
PU pa;
实际上就是用 pa
替换 void (*PU)(int a, char b);
中的 PU
。
这也就是编译器处理tyepdef定义的原理。
typedef可以看作是定义了一个新的类型,这个类型在解释的时候按照以上规则定义变量。
typedef与结构体搭配也可以用上面的理论解释:
使用typedef关键字定义结构体类型 | 定义结构体类型的同时定义结构体类型变量 |
---|---|
typedef struct student { int age; int height; }std; // std相当于struct student |
struct student { int age; int height; }std1,std2; //定义了student数据类型的结构体和std1、 std2结构体变量 |
04 char有符号位
在x86平台上,默认有符号,但在arm平台上默认没符号。
保险起见,用 signed char
05 enum(枚举常量)
定义类似于结构体
相当于一次性定义多个常量。
用它定义变量没用
06 可变数组长度
老师可能讲的是在C语言中不能用变量作为数组的长度,但实际上, 哪怕是c89的标准,在满足一定条件时也是能够用变量作为数组长度的。
例如:
int len = 3;
char a[len];
在语法上是没有问题的。
尽管如此,我本人还是不推荐使用这种写法。
- 这样写无法在定义的时候初始化。
char a[len] = {0}
就是错的。 - 这样写只能作为局部变量,如果定义为全局变量就有问题。
0x04 指针专题
01
-
字符串常量有返回值,其返回值为首字母的地址
-
数组名,是指向它的第一个一级成员的指针
数组名取地址,是指向整个数组的指针
02 表述 *(p + 1)
与 p[1]
等价
本质是因为 []
是下标运算符。 x[y]
等价于 *((x) + (y))
03 函数指针
为方便起见,C语言中可以将函数指针直接按函数调用的方式使用。比如func(u,v)
与(*func)(u,v)
等价
最常见的例子:qsort里面的cmp函数。cmp函数的名字是你自己定的,qsort在用你写的cmp的时候先给它一个别名。
这个过程就是函数A的地址通过一个函数指针的形参传递到另一个函数B内部,从而能够在内部调用。只要函数A格式与B里面的形参一样,A名字可以任何。
大大增加了程序的扩展性。
04 二维数组与二重指针
见此博客
具体原因是两者在内存中的分配不完全一样,二重指针更加分散一些
所以 char **p
和 char p[2][3]
之间不能相互传递参数
0x05 结构体专题
结构体大小(sizeof)
没有成员的结构体占用的空间是1个字节。
偏移量
struct stru
{
int a; //start address is 0
char b; //start address is 4
int c; //start address is 8
};
偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。显然,结构体变量中第一个成员的地址就是结构体变量的首地址。
比如上面的结构体,第一个成员a
的偏移量为0。第二个成员b
的偏移量是第一个成员的偏移量加上第一个成员的大小(0+4),其值为4;第三个成员c
的偏移量是第二个成员的偏移量应该是加上第二个成员的大小(4+1)。
但在实际中,存储变量时地址要求对齐,编译器在编译程序时会遵循两条原则:
(1)结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
(2)结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。
0x06 函数专题
01 qsort
第一个参数是地址,注意首项零的情况
第二个参数是个数
qsort在调用外部判断函数的时候,传入的参数是 数组元素的地址! 。在自己写cmp函数的时候尤其要注意。
另外,指针在C语言中 指向的是被指元素的首地址
qsort默认从小到大排序,若cmp返回值大于0则交换,小于等于0不管。通常直接写return a - b;
一维的数组排序
int a[1000]
qsort(a,1000,sizeof(int),comp);
int comp(const void *a,const void *b)
{
return *(int *)a-*(int *)b;
}
-----------------------------------
字符数组排序
char a[1000][20];
qsort(a,1000,sizeof(char)*20,comp);
int comp(const void *a,const void *b)
{
return strcmp((char *)a,(char *)b);
}
-----------------------------------
结构体排序
typedef struct str
{
char str1[11];
char str2[11];
}str;
str *strs;strs=(str *)malloc(sizeof(str)*10);
int compare(const void *a,const void *b)
{
return strcmp(((str*)a)->str2,((str*)b)->str2);
}
qsort(strs,10,sizeof(str),compare);
02 strlen()
返回值为无符号整形unsigned int
。故如果参与减法运算会产生负数,需要强制类型转换
03 内存分配函数
i malloc()
功能 | 申请堆内存 |
---|---|
所需头文件 | #include<stdlib.h> |
函数原型 | void *malloc(size_t,size); |
参数(size) | 所申请的一块堆内存的大小,单位是字节 |
返回值 | 成功 - 指向分配好的堆内存的指针 失败 - NULL |
malloc()分配给定大小(以字节为单位)的内存块,并返回一个指向块开头的指针。malloc()不会初始化分配的内存。如果在初始化之前我们尝试访问内存块的内容,那么我们将得到分段错误(或者可能是垃圾值)。 |
ii calloc()
功能 | 申请堆内存 |
---|---|
所需头文件 | #include<stdlib.h> |
函数原型 | void *calloc(size_t count,size_t size); |
参数(count与size) | size ---- 所申请的一块堆内存的大小,单位是字节 count ---- 所申请的堆内存的块数,所有的内存块是连续分布的,无间隔的 |
返回值 | 成功 - 指向分配好的堆内存的指针 失败 - NULL |
calloc()分配内存并将分配的内存块初始化为零。如果我们尝试访问这些块的内容,那么我们将得到 0。 |