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


8进制与16进制

0x00 计算机原理

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

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

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

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

03 ++--的作用顺序

一般来说,++a表示先算++再算a但是,如果表达式里出现了多次变量,如++a = a + (a-1),情况就不确定了。在使用++--时需谨慎。

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值,文件指针不变还是处于原来的地址。

04 fscanf与fgets与\n

fscanf遇到\n和空格停止,但fgets会读进去。
建议用fgets去除\n,fgetc不一定行

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] 之间不能相互传递参数

05 文件位置指针

对文本文件的读写也是指针实现的。

#include<stdio.h>
int main()
{
    FILE *fp, *dd;
    char x[100];
    fpos_t pos[10];
    long int pos2 = 0;
    int top = 0;
    int i ;
    
    fp = fopen("in.in","r");
    //puts("fd");
    dd = fp;
    while(feof(fp) == 0)
    {
	    fgetpos(fp, &pos[++top]);
	    fgets(x, sizeof(x) / sizeof(char), fp);
	    //pos2 = ftell(fp);
	    printf("pos[%d]=%ld",top, pos[top]);
	    puts(x);
	    //printf("pos2=%d\n", pos2);
	    //fseek(dd, pos2, SEEK_SET);
    }
    for(i = 1; i <= top; i++)
    {
	    fsetpos(dd, &pos[i]);
	    fgets(x, sizeof(x) / sizeof(char), dd);
	    printf("%d:", i);
	    puts(x);

    }
return 0;
}

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 宏定义专题(待整理)

宏定义详解

posted @ 2022-08-30 23:16  砥安  阅读(234)  评论(0编辑  收藏  举报