C语言进阶语法及部分注意事项


目录

8进制与16进制

0x00 计算机原理

01 缓冲区(buffer)与缓存(cache)

buffer是内存的一部分
cache是CPU,磁盘等的一部分
简单来说就是buffer偏重于写,而cache偏重于读。

02 一个程序运行时的内存空间

栈区(stack) 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等
堆区(heap) 一般由程序员分配释放(malloc), 若程序员不释放,程序结束时可能由OS回收
全局区(静态区)(static) 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,
未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放
文字常量区 一般常量就是放在这里的。不能被修改。 程序结束后由系统释放
程序代码区 存放函数体的二进制代码

0x01 输入专题

01 scanf

强烈推荐这篇博客,一定要去看看:scanf函数读取缓冲区数据的问题

  1. scanf(" ");如果双引号里面的空格可以跳过广义上的空格,等价于循环+getchar

  2. 正则表达式

    参考博客

    • []内是匹配的字符。

    • ^表示求反集,当遇到非集合内的字符时立即终止输入。

    • %表示选择,%* 表示跳过,其后一定要有新的%语句,否则无法读入。

    • %,%*后面的是条件,比如%ss是一个条件,表示任意字符。%3s又多了一个条件:只拷贝3个字符。 %[a-z]的条件稍微严格一些,输入的东西不但是字符,还得是一个小写字母的字符。
      ,也就是跳过满足条件的字符,

    • 注:%[a-b 0-9]能够读入空格。单个字符也可以直接写在中括号里面

  3. fgets(c, n, fp)gets 不同,第一、fgets 需要加最大输入长度n这个参量, 表示加了 /0 之后 fgets 能读入的最大长度。因此需要在读入的最末尾主动赋值为 /0 ,不必管是否有 /n,这样能保证数据没有多余的 /n

  4. 正文文件fscanf 等函数大都和标准I/O下的函数用法一样。除了 fgets

02 sscanf

本质上与scanf相同,只不过将目标字符串当作stdin

同理还有sprintf

0x02 文件专题

01 fopen

  1. fopen 必须要搭配文件指针使用,如 fp = fopen( , ) 而且得配合 fscanf 等等,但是在关闭文件之可以同时对文件和标准输入输出进行操作。

  2. 文件打开一定要判断是否成功。如果不成功,可以使用 perror(const char *s) 它可将 上一个函数(一般是各个库函数) 发生错误的原因输出到标准设备(stderr)。参数s所指的字符串会先打印出,后面再加上错误原因字符串。

02 文本方式打开与二进制方式打开

C语言采用文本方式和二进制方式打开文件的区别分析

i. 区别

数据怎么在磁盘上写不是由文件打开方式决定的,而是由写函数决定的。数据怎么从磁盘上读也不是由文件打开方式决定的,而是由读函数决定的。
上面说的数据怎么写是指:一种类型的变量是怎么存的?比如int 12,可以直接存12的二进制码(4个字节),也可以存字符‘1’,字符'2'.
数据怎么读的是指:我要读一个int变量,是直接读sizeof(int)个字节,还是一个字符一个字符的读,直到读到的字符不是数字字符。

这对应了两种函数

ii. 如何读写

  1. fread 以二进制的方式读入,fscanf 以正文方式读入。
    fread 可以直接按几个字节为单位读,而 fscanf 则是把文章解释为字符串然后再读。

  2. 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 );
  1. 它中间的那个参数是long类型,因此中间那个参数可以这样写:22LL表示这个整数是long类型。
    需要注意的是,offset移动的量永远是字节数。 由于二进制文件和文本文件存储格式的区别 ,需要自己手动计算偏移量
  2. 第三个参数只有三个值SEEK_SET表示文件开头,SEEK_CUR表示当前指针位置,SEEK_END表示文件末尾
  3. 当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 * pint 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];

在语法上是没有问题的。

尽管如此,我本人还是不推荐使用这种写法。

  1. 这样写无法在定义的时候初始化。char a[len] = {0}就是错的。
  2. 这样写只能作为局部变量,如果定义为全局变量就有问题。

0x04 指针专题

01

  1. 字符串常量有返回值,其返回值为首字母的地址

  2. 数组名,是指向它的第一个一级成员的指针
    数组名取地址,是指向整个数组的指针

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 **pchar p[2][3] 之间不能相互传递参数

0x05 结构体专题

结构体大小(sizeof)

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语言中 指向的是被指元素的首地址

示意图b

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。

0x07 宏定义专题(待整理)

宏定义详解

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/283208.html

(0)
上一篇 2022年8月31日
下一篇 2022年8月31日

相关推荐

发表回复

登录后才能评论