C 知识点

目录

找到一个数的个位、十位、百位、千位

n/1%10;  //个位
n/10%10;  //十位,
n/100%10;  //百位
n/1000%10;  //千位

功能型函数

  • sleep函数在windows中,其定义在 <windows.h> 头文件中,在linux中,其定义在<unistd.h>

  • 注释:// /**/

  • fabs(a < b) 比较两个浮点数,需要头文件 math.h

  • printf(“x = %d”,x); printf(“x = %-n.mf”,x),n为最小字符宽度,m为精度,- 号代表右对齐

  • printf(“x = %*.*lf”,n1, n2, n3) //宽度为n1,精度为n2,打印n3

  • sizeof (name) ,name 数组总的字节数, 并不是字节长度,%zd,变量可用括号可不用,但是数据类型一定要括号,例如sizeof (int)

  • strlen (name) ,name中字符串的长度,不包括空字符,需要头文件string.h

  • pow(3.5,2.2) ,3.5的2.2次方 需要头文件<math.h>,并且需要连接库-lm,即gcc test.c -o test -Wall -lm,返回就是幂运算的结果

  • m % n ,得m除n的余数,实质为 m-(m/n)*n (其中m/n舍掉小数部分)

  • #include <stdio.h>
    #include <time.h>
    int main(void)
    {
    	int i;
    	srand(time(NULL));   //每次出不一样随机数的种子,需要头文件
    	for(i = 0; i < 10; i++)
    		printf("%d\n", rand());   //rand(),打印随机数,可以用取余来缩小范围,time函数会获取时间戳
        	printf("%d\n", rand() % 100);
    
    	return 0;
    }
    
    
  • rand() 可以打印任意范围的随机数

    //生成n到m范围的随机数,a为n到m中间有多少个数(即a = m - n + 1)
    rand() % a + n;   //n即使是负数也成立
    

支持一次初始化多个变量,用逗号隔开,也可使用连等

数据类型

  • uint8_t:实质上就是 unsigned char 类型,头文件为 #include <stdint.h>

  • uint16_t:实质上就是 unsigned short 类型,头文件为 #include <stdint.h>

  • uint32_t:实质上就是 unsigned int 类型,头文件为 #include <stdint.h>

  • int占16或32位,最小取值范围[-32767,32767] 4字节

  • char 占1个字节

  • float,最少表示6为有效数字 占4字节

  • double,最少表示10位有效数字 占4字节

  • long double 2.3L 占12字节

  • short 占16位,最小取值范围[-32767,32767] 占2字节

  • long 占32位,最小取值范围[-2147483647,2147483647] 2L 占4字节

  • long long 占65位 2LL:以longlong类型把2进行存储 占8字节

  • unsigned int: unsigned表示非负值,所以取值范围[0,65535]

  • unsigned long:取值范围为[0,4294967295] 2uL

  • _Bool:布尔型,1--true 0---false

打印short、long|、long long和unsigned类型

  • int %d

  • char %c单个字符 %s 字符串

  • float %f:浮点数 %e:指数计数法 %g:打印小数点后面的有效位(默认有效位为6位)

  • double %lf:浮点数 %Le:指数计数法

  • long double 占12个字节, 用%Lf输出

  • 十六进制表示小数: %a

    例:0xa.1fp10 :0x表示十六进制,a、1、f在十六进制中分别为10、1、15,p10为2的10次幂

  • short %hd 八进制%ho 十进制%hd

  • long %ld 八进制%lo 十六进制%lx

  • long long %lld

  • unsigned int %u

  • unsigned long %lu

  • unsigned long long %llu

  • sizeof(int/float/……) %zd

  • strlen(name) %zd

进制表示

  • 八进制:前缀为0,代码中用%#o

  • 十六进制:前缀为0x或0X,代码中用%#x

  • 例如printf("octal = %o; hex = %x\n",100,100); 输出无前缀八、十六进制数,

  • printf(“octal = %#o;hex = %#x\n”,100,100);输出带前缀的八、十六进制数,#是加前缀的意思

头文件

  • #include <stdio.h>

  • #include <string.h> :包括strlen函数

  • #include <ctype.h> :包括许多字符函数

  • #include <stdbool.h> :bool类型的,false代表0,true代表1

  • #include <limits.h> :提供了 CHAR_BIT 的定义,他表示每位字节的位数

运算符优先级

单算移关与,异或逻条赋,逗

  1. 单目运算符:+ - ++ -- ~ *
  2. 算数运算符:+ - * / %
  3. 移位运算符:<< >>
  4. 关系运算符:> < == != <= >=
  5. 与:& 10&7 = 1010 & 0111 = 0010
  6. 异或: ^ 0^0 = 0 0^1=1 1^1=0
  7. 或:| 0|1 = 1 0|0 = 0 1|1 = 1
  8. 逻辑运算符:&& || !
  9. 条件运算符(三目):? :
  10. 赋值运算符:= += -= *= /= %= <<= >>=
  11. 逗号运算符:, b = (3,4)

注意:运算顺序

  • 单条赋:运算方向从右向左,
  • 其他:运算方向从左向右

整数溢出:当值到最大值后,会重新从起点开始

printf 和 scanf

  • printf(“%*.*f”,a,b,n); 字长为a,精度为b,输出n

  • printf("%02d", n) 占2位,如果没有占满2位的话前面补0

  • printf("%4d", n) 占4位,如果没有占满4位的话前面补空,这就是他自动右对齐的原有

  • printf("a = %d\r", a); //-r是回车,并不换行,把光标移到最前面,这里就是"a = ”中a的位置
    printf("b = 234\n");  //这个时候由于光标没有换行,所以b = 234会覆盖a = %d中的内容
    
  • scanf(“%*d %*d %*d %d”,&a); 跳过输入的前3个数,把第四个数赋给a,scanf("%*c") 和 getchar() 效果一样

  • scanf()读入字符串时,不需要使用&

  • scanf(“%s”,a):把输入解释成字符串,从第一个非空白字符开始到下一个空白字符之前

  • scanf("%[^\n]", a) 可以避免上面的情况,他会把除了\n之外的所有字符都输入进去,^是除了的意思

  • scanf 有返回值,返回赋值成功的个数.

const只读

○const int PI = 3.1415926 :不可更改

typedef 别名

○typedef double real; //real就是double的别名,可用real 来初始化变量效果和double 一样

i++ 和 ++i 的区别 P117

例如b = i++ :先把i赋值给b,然后i自加一,(先使用,再递增)

​ b = ++i; 先把让i自加一,然后赋值给b (先递增,再使用)

打印特殊符号

用转义字符

%% 打印百分号

\ 打印\

\’ 打印’

\” 打印”

? 打印?

循环

入口条件循环

  1. while

    while (i < 10)  //循环条件  如果不加{}则 只执行下一条语句。即以分号结尾
    {
        循环体
    }
    
  2. for

    for (语句1;语句2;语句3)
    {
    	循环体
    }
    
    • for(语句1;循环条件;语句3) :for循环一开始会执行语句1(这里可以初始化,也可以是printf语句);然后判断循环条件,然后进行循环,循环一次后再执行语句3

    用法:

    • 改变语句一和语句3,可以让计数器从任意值开始,每次递增任意值

    • 语句一 可用printf代替

    • 可以省略语句,但不可省略分号

    • 可以在1、3语句中加逗号。多次初始化,多次赋值,例 For(a = 1, b = 2,c = 3;a < 10;a++,c += a)

出口条件循环

do ……while

do
{
  循环体
}while (条件);   //最后有分号

第七章

条件语句if

if (条件)
{
   语句1
}

else if (条件2)
{
   语句2
}

else if (条件3)
{
    语句3
}

else
{
    语句4
}

getchar putchar 专门针对字符输入输出

  • 用法:
 ch = getchar();       //输入
 putchar(ch);       //输出

逻辑运算符

与或非 && || !

条件运算符 ?:

  1. 例如: x = (y < 0) ? -y : y;,它等价于
if (y < 0)
      x = -y;
else 
      x = y; 

continue和break

  • Continue跳出当前循环,对于while do…while都是直接跳到下一次的测试条件语句,而for循环要先执行语句3,然后在执行下一次的测试条件
  • break跳出整个循环,即使是for循环也是直接跳出循环执行下一条指令。

switch语句

switch (整数值包括char)
{
     case 1: statement1;
     case 2: statement2;
             (return 0;)  //退出函数,回到函数被调用的地方
     default: statement3;
}

goto语句

goto part2;  //无条件跳转,然后按顺序执行           
   …
part2:statement;

程序

  1. //计算字符串中单词的个数
    int n = 0;
    char str;
    bool x = false;   //在单词内置为ture
    
    while ((ch = getchar()) != EOF)
    {
        if (isspace(ch) && !x)   //isspace(ch)当ch为空白字符时为真
        {
            x = true;
            n++;
        }
        
        if (isspace(ch) && x)
            x = false;
    }
    

第八章

重定向输入

./test<text :text作为可执行文件test的输入

重定向输出

./test>text : 执行test文件,然后把输出结果放入text中(覆盖文件的内容)

./test>>text :执行test文件,然后把输出结果放入text中(追加)

组合重定向

(./)test<text1>text2 :将text1作为可执行未见test的输入,并将输出结果保存到text2中

(./)test>text2<text1 :同上

需要用((ch = getchar()) != EOF)时,ch应该为int 类型

多文件

  1. //a.c文件
    #include "custom.h" //这里调用了两个函数,所以两个函数原型必须要写
    int main(void)
    {
        ……
        func1();
        func2();
        return 0;
    }
    
  2. //b.c文件
    #include “custom.h"  
    //实际上函数原型是哪里调用了函数就在哪个文件里面写,所以这里实际上可以不用写函数原型
    void func1(void)  //函数实现
    {
        //实现
    }
    
    void func2(void)  //函数实现
    {
        //实现
    }
    
  3. //custom.h文件
    #include <stdio.h>
    #include <ctype.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdbool.h>
    
    void func1(void);   //函数原型
    void func2(void);  //函数原型
    
  4. 运行时候要一起运行

    gcc a.c b.c -Wall

几种常见的验证实例:

  1. 处理多余的输入

    While(ch != ‘\n’)
    	continue;
    
  2. 验证数字输入

     scanf  读取数据
     while (出错的情况下)
        处理  //此处处理可以用while (getchar() != ‘\n’)  或者用//scanf(“%*s”),把错误的输入丢弃
         
    while ((status = scanf(“%d”, &n)) != 1)
    {
    	if (status != 1)
    		while (getchar() != '\n')
                continue;
        printf("Enter error, enter again\n");
        
        //如果后面还有字符输入就需要去掉多余回车符
        while (getchar() != '\n')
            continue;
    }
     
    
  3. 处理switch输入

    While ((choice = F()) != ‘q’)
    {
        switch (choice)
        {
    
          case 1:
          case 2:
          ....
          Default: (输入错误)
        }
    }
     
    F()
    {
        int ch;
        界面
        ch = getchar();
        /*
        While ((ch < ‘a’|| ch > ‘c’) && ch != ‘q’)
        {
            printf("Please enter you choice");
            while (getchar() != '\n')
                continue;
         ch = getchar();
        }
        */
        While ((ch < ‘a’|| ch > ‘c’) && ch != ‘q’)//while (strchr("abcq", ch) == NULL)
        {
    		if (ch != '\n')
    			while (getchar() != '\n')
                    continue;
            puts("Enter error, enter again.");
        }
        while (getchar() != '\n')
            continue;
        return ch;
    }
    

第九章函数

  1. int F(int n1, int n2); //n1 n2 可以省略 (int, int),函数声明也可以在main函数中,但是必须在使用函数之前\

  2. 指针

    间接运算符(& *)

    • & :后面跟一个变量时,&给出该变量的地址

    • * :后面跟一个地址或者指针名时,*给出存储在指针指向地址上的值

  3. 定义指针变量:

    看指针存储的变量是什么类型就定义什么类型指针变量

    • 如 int * xd float * yd char * chd
    • 定义函数时:void F(int * xd, int * yd) //其中变量(xd, yd)也可以省略
  4. #include <stdio.h>
    #double power (double x1, int x2);  //有分号
    Int main(void)
    {
          …
          Y = power (x1, x2)  //主函数中使用函数,并赋值给Y
          ……
          return 0;
    }
    
    double power(double x1, int x2)  //定义函数
    {
      double y;
       …
       …
      return y;      //要有返回值
    
    }
    
  5. 函数简写

    #include <stdio.h>
    
    int function(int x, int y) {return x + y;}  //分号在花括号内
    
    int main(int argc, const char *argv[])
    {
    	int x = 3, y = 5, z;
    
    	z = function(x, y);
    	printf("z = %d.\n", z);
    
        return 0;
    }
    

第十章:

数组:

补充:

  • 数组的类型,char a[10];,此时数组 a 的类型为 char [10] ,即 sizeof (a)和sizeof (char [10]) 相等
  1. 数组初始化

    • int pow[8] = {1, 2, 4, 6, 8, 18, 32, 64},如果不初始化,里面装的都是垃圾值,如果初始化一部分,那另一部分就为0

    • 而且初始化也可以用const声明数组 const int pow[2] =

    • 也可以省略数组尺寸,让编辑器自动匹配数组大小和初始化列表中的项数,如 int day[] = {1,2, 3, 4},而数组尺寸可以用sizeof day / sizeof day[0] 来计算,整个数组大小除以单个元素占用的大小。

    • 可以特定只初始化数组里面某个值,例如int day[10] = {31, 28, [4] = 31, 30, 31, [1] - 29};,数组结果如下:

    • 如果未指定数组元素大小,例如int day[] = {1, [6] = 23}; //编译器会把数组大小设置为足够装得下数组的值,所以此数组大小为7

    • 数组不允许数组给数组赋值,也不允许用花括号(出初始化之外)

    • char ar[2][10] = {"ruankai", "fankewei"}; 初始化字符串数组

    • 判断下面哪些是定义的字符串

      char str[5] = {'a', 'b', 'c', 'd'};  //是字符串数组,里面没装满,最后一位就默认初始化成 '\0'
      char str[5] = {'a', 'b', 'c', 'd'};  //和上面刚好相反,不是字符串数组
      
  2. 多维数组

    • float A[5][12]; //内含5个元素的数组,每个元素都是12个float类型元素的数组,其中2行3列元素为A[1][2]
    • 初始化二维数组:int A[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}},也可以省略内部{},因为在内存上,二维数组是连续的
    • 其他多维数组 int A[3][3][3]
  3. 数组指针

    数组名称一般都是数组第一个元素的地址,即A 等于 &A[0]

    • A + 2 等于 &A[2], *(*(A + i) + j) 和 A[i][j]相同
  4. 数组函数

    数组名实质上是一个地址,所以函数声明中要用指针变量来声明,例如

    • int sum(int * A, int Size) 声明中A 和 Size可以省略,但是函数定义中不能省略。
    • 也可以用int sum(int A[], int Size) 来声明

    还可以定义起始到结束,例如

    int sum(int * start, int * end);
    int main(void)
    {
    	int answer, A[Size]
    	
    	answer = sum(A, A + Size)
        …
    }
    
    int sum(int * start, int * end)
    {
       int total = 0;
       
    	while (start < end)
    	{
    		total += *start;
     		start++;
     		…
    	}
    }
    
  5. 保护数组中的数据

    • 如果定义的函数不需要更改数组里面元素的值,就可以使用const来避免错误,例如函数声明和定义中加入const,int sum(const int * ar, int n);

    • 如果const 后面是指针,则表明不能使用该指针更改他所指向的值。

      *ptr = 2; //不允许
      ptr[2] = 2; //不允许
      ptr++;  //允许,并没有改变指针所指向的值
      
    • const可以声明并初始化一个不能指向别处的指针,关键是const的位置

      • double * const pc = array;  //pc 指向数组的开始
        pc = &array[2]  //不允许,该指针不能指向别处,但可以修改指针指向的值
        *pc = 2;  //允许
        pc++  //不允许
        
    • 综合,可以const double * const pc = array;不能指向别处的指针,也不能用该指针来改变值

    • 指针类型必须要匹配,例如const类型的数组,想要用指针指向该数组时,这个指针必须也是const型

  6. 指向多维数组的指针

    • int (* ptr) [n] //ptr指向一个内含n个int类型值的数组
  7. 指针兼容:不同类型的指针不能相互赋值

  8. 二维数组函数

    • void somefunction(int (*A) [col], int row); 或者
    • void somefunction(int A[] [col], int row); 或者
    • void somefunction(int [][col], int);
    • 多维: void somefunction(int A[][12][20][30], int row);
  9. 变长数组函数

    • void somefunction(int row, int col, int ar[row][col]); 或者

    • void somefunction(int, int, int ar[*][*]);

      somefunction(row, col, array);

  10. 复合字面量

    • 类似 int array[5] = {1, 2, 3, 4, 5};(int [5]) {1, 2, 3, 4, 5};

第11章

字符串数组初始化

  1. const char ml[40] = "I am me";   //一般初始化
    //标准初始化,但是太麻烦了,注意后面\0不能省略,不然就不是字符串,而是字符数组
    const char ml[40] = {'I', ' ', 'a', 'm', ' ', 'm', 'e', '\0'};
    //也可以让他自己匹配
    const char ml[] = "I am me";
    //也可以声明变长数组
    char ml[n]
    //指针表示法创建字符串
    const char * pt1 = "li jia big cai ji";
    
  2. 判断下面是否是字符串数组

    char ar[5]
    
  3. 数组形式的字符串数组和指针形式的字符串数组的区别

    • 初始化数组就是把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针

    • 数组形式的数组名是个常量,而指针形式的是个变量,所以数组名不能自加,指针可以自加

    • 如果不修改的字符串数组,就用指针形式的,如果要改变字符串或者为字符串输入预留空间,就是用数组形式的

puts

  1. puts() 函数属于stdio.h,只显示字符串,而且自动在显示的字符串末尾加上换行符,他后面接受的是一个地址

    char word[80] = "I an a string in an array.";
    const char * pt1 = "something is pointing at me.";
    puts("here are some strings");
    puts(words);     //等同于printf("%s\n", words )
    puts(pt1);
    
    • 如果要打印“,可以使用转义字符 \
  • 他只有遇到空字符才会停止,puts('w', 'o', 'w', '!'); 这样就没有空字符,它会一直打印,直到找到空字符为止

gets

  • 该命令读取整行输入,直至遇到了换行符,然后丢弃换行符,存储其余字符,并在这些字符的末尾添加一个空字符使其成为一个C字符串

fgets()和fputs()

  1. fgets(str, n, stdin)
    • 第一个参数是把读取的字符串存入str数组
    • 第二个参数是读入字符的最大数量。如果是n则只能读入n-1个字符,或者遇到第一个换行符为止,如果超过n个字符,它会截断,然后在数组最后一个元素填充 '\0',最后输出也就是 n-1 个元素
    • 第三个参数指明要读入的文件,从标准输入的话写 stdin
    • 另外,他会把第一个读取到的换行符存储在字符串中
    • fgets 返回值为指向char的指针,一般返回的地址与第一个参数地址相同,读取失败或读到文件结尾返回NULL。
  2. fputs(array,stdout)
    • 第一个参数是要输出的字符串数组
    • 第二个参数知名他要写入的文件,stdout (标准输出)
    • 它不会再输出最后添加换行符
//该函数可以显示任意长度字符串,通过循环,先打印一部分,然后循环在打印一部分
#include <stdio.h>
#define Size 10
int main(void)
    
{
	char words[Size];
	puts("Enter string :");
	while (fgets(words, Size, stdin) != NULL && words[0] != '\n')
		fputs(words, stdout);
	puts("Done!");

	return 0;
}


//下面代码能够把fgets最后保留的换行符替换成空字符,而且遇到结尾后就不会继续读入
    //如果读到文件结尾(或者按下ctrl + D)或者一开始就回车就直接跳出循环
	while (!(fgets(words, Size, stdin) == NULL || words[0] == '\n')) 
	{
        //读取到换行符或者空字符就跳出循环
		for (i = 0; words[i] != '\n' && words[i] != '\0'; i++)
			continue;
		if (words[i] == '\n')   
			words[i] = '\0';     //把换行符换成空字符
		else 
			while (getchar() != '\n')   //把多余的缓存输入去掉
				continue;
		puts(words);
	}

gets_s()

  1. get(words, Size)
    • 两个参数,和前面的差不多
    • get_s() 只从标准输入中读取数据,所以不需要第三个参数
    • get_s() 读到换行符,不会丢弃换行符,会储存他
    • 当超出最大字符数时还没有读取到换行符,他会把目标数组中的首字符设置为空字符,读取并丢弃随后的的输入直到读到换行符或文件结尾,然后返回空指针。

scanf

  1. scanf("%ns") 从第一个非空白作为字符串开始,以下一个空白字符或者指定字段宽度n结束

printf

  1. 适合打印多个字符串

代码替换

  1. while (fgets(words, Size, stdin)) 等同于 while (fgets(words, Size, stdin) != NULL,如果 fgets() 读到文件结尾会返回空指针,c中宏定义为 NULL ,代码中可以用0代替

  2. 对指针中NULL任何的读写操作都是非法的

  3. while (*string) 等同于 while (string != '\0') ,同理上

    //下面语句printf打印put2的值,但是为了获取put2的值,计算机必须先执行put2,所以先执行put2在执行printf
    printf("I count %d characters.\n", put2(x));
    

s_get函数

  • s_get函数 ,去掉fgets 里面的换行符

    char *s_gets(char * str, int n)
    {
        char * y;
    	int i = 0;
    	
        y = fgets(str, n, stdin);
    	if (y)
    	{
    		while (str[i] != '\n' && str[i] != '\0') 
    			i++; 
    		if (str[i] == '\n')
    			str[i] = '\0';
    		else //如果先是空字符,那么就把残留在输入缓冲区里面的字符吃掉
    			while (getchar() != '\n')
    				continue;
    	}
      return y;
    }
    
  • 改进的函数

    char *s_gets(char * str, int size)
    {
        char *out, *find;
        
        out = fgets(str, size, stdin);
        if (out)
        {
            find = strchr(str, '\n');
            if (find)
                *find = '\0';
            else  //如果find没有找到\n,那么说明输入的字符串超过size,
                whilel (getchar() != '\n')   //此时会有残留字符串在输入缓冲区,需要吃掉
                	continue;
        }
        
        return out;
    }
    

都在 string.h 头文件中

  1. strlen( )函数

    • strlen (name) 计算name中字符串的长度,不包括空字符
  2. strcat( )函数

    • 该函数接受两个字符串数组,他会把第二个字符串的备份附加在第一个字符末尾(此时会覆盖第一个字符串末尾的空字符),并把拼接后的结果赋给第一个字符串变量,第二个字符串不变
    • 该函数返回第一个参数的地址,指针变量
    • 但是strcat函数并不会检查第一个字符串数组是否容得下第二个字符串数组
    • 注意输入用fgets的话要去掉'\n' ,否则打印的话会在两行
  3. strncat( )

    • 该函数和strcat类似,但是有第三个参数,指定最大添加字符数,例如 strncat(bugs, addon, 13) ,会把addon中的字符串内容附加给bugs,在加到第十三个字符或遇到空字符停止,所以第一个数组大小应该足够大
    • 同上要注意fgets要去掉'\n'
    • 该函数在附加时,会覆盖第一个参数后面的空字符,并且他不会拷贝第二个参数里面的空字符及其后面的字符,然后再拷贝字符末尾添加一个空字符
  4. strcmp( )函数

    • strcmp(str1, str2) ,比较两个字符串,如果字符串相同,就返回0,否则返回非零值
    • 注意用 fgets 函数读入时,会有换行符的干扰,这时候用 s_gets 函数
    • strcmp会根据str1,str2字母在ASCII表中的位置来返回正值或者负值,例如strcmp("A", "A") = 0, strcmp("A", "B") = -1, strcmp("C", "B") = 1 ,在不同系统中返回的值不一定是1或者-1,最好用大于小于0来判断
    • 注意strcmp 是比较字符串的("A"),不是字符('A')
  5. strncmp()函数

    • strcmp 比较两个字符串中的字符,直到发现不同字符为止,这一过程可能会持续到字符串末尾,而 strncmp 函数可以指定比较的位置
    • strncmp(str1, str2, 5) ,只比较前五个字符,相同返回0,否则返回非零值
  6. strcpy()函数

    • strcpy(str1, str2) 把str2里面的字符串拷贝到第一个str1指向的数组中(包括空字符),注意第一个参数的应该分配足够的空间

      strcpy(str1, "caiji");
      
    • strcpy函数返回类型是 char * ,它返回的是第一个参数的值(即第一个参数指向的位置),而且strcpy 也不一定指向数组开始

      const char * str1 = "beast";
      char strar[40] = "be the best that you can be.";
      char * str2;
      
      str2 = strcpy(strar + 7, str1);   //strar输出 "be the beast"
                                        //str2输出 "beast" strar[7]开始	
      
  7. strncpy函数

    • 类似于 strcpy ,它可以指定拷贝的最大字符串
    • strncpy(str1, str2, n) ,只拷贝n个字符,注意 n <= Size - 1 ,当然如果先遇到了空字符,那就不用拷贝n个
    • 如果还没遇到空字符就结束了,那就说明第二个字符的空字符没有被拷贝过去,所以要手动添加
  8. strsep函数

    • 函数原型:char *strsep(char **stringp, const char *delim);

    • 功能:分解字符串为一组字符串。stringp为要分解的字符串,delim为分隔符字符串。

    • 参数:

      • stringp:需要分割的字符串
      • delim:按该字符分割
    • 返回值:返回下一个分割后的字符串指针, 如果已无从分割则返回NULL。

    • 案例:

      #include <stdio.h>
      #include<string.h>
      int main()
      {
        char str[] = "mv a.c b.c";
        char *p;
        char *buff;
        buff=str;
        p = strsep(&buff, " ");
        while(p!=NULL)
        {
          printf("%s\n", p);
          p = strsep(&buff, " ");
        }
        return 0;
      }
      
  9. sprintf 函数

    char str1[10] = "ruankai"; 
    char str2[20] = "lijia";
    char str3[60];
    double n = 100;
    
    //等同于printf("%s and %s is caiji %lf\n", str2, str1, n),然后把字符串储存在str3中
    sprintf(str3, "%s and %s is caiji %lf\n", str2, str1, n);
    puts(str3);
    
  10. 其他补充函数

  • char * strchr(const char * s, int c);

    如果s字符串中包含c字符,该函数返回指向s字符串首次出现的c字符的指针(空指针也在查找范围内),如果在字符串s中未找到c字符,该函数返回空指针

  • char * strrchar(const char * s, int c)

    该函数返回s字符串中c字符的最后一次出现的位置,如果在 s 中没有找到 c 字符 ,则返回空指针

  • char * strpbrk(const char * s1, const char * s2)

    如果 s1 字符中包含 s2 字符串中任意字符,该函数返回指向 s1 字符串首位置的指针;如果在 s1 字符串中未找到任何 s2 字符串中的字符,则返回空字符

  • char * strstr(const char * s1, const char * s2)

    该函数返回指向 s1 字符串中的 s2 字符串出现的首位置,如果在 s1 中没有找到 s2 ,则返回空指针。

  • size_t strlen(const char * s)

    该函数返回s字符串中的字符数,不包括末尾的空字符。

应用:简化s_gets函数

char line[80];
char * find;

fgets(line, 80, stdin);
find = strchr(line, '\n');      //查找换行符
if (find)         //如果没有找到,返回NULL
    *find = '\0';    //找到把该处的字符替换为空字符
else
    while (getchar() != '\n')   //处理多余字符
        continue;

选择排序算法

排列字符串数组,利用 strcmp 函数比较

  1. //使用指针交换两个字符串
    char str[Row][Col] = {"ABC", "adfj", "fhskah","fhsah"};
    char * ptr[Row];
    char * temp;
    
    for (int i = 0; i < Row - 1; i++)
        for (int j = i + 1; j < Row; j++)
            if (strcmp(ptr[i], ptr[j]) > 0)
    		{
    			temp = ptr[i];
    			ptr[i] = ptr[j];
    			ptr[j] = temp;
    		}
    
    
  2. //每个每个字符的交换
    char temp[Col] = "";
    
    for (int i = 0; i < 4; i++)
    	for (int j = i + 1; j < 5; j++)
    			if (strcmp(str[i], str[j]) > 0)
    				for (int k = 0; k < 10; k++)
    				{
    					temp[k] = str[i][k];
    					str[i][k] = str[j][k];
    					str[j][k] = temp[k];
                    }
    

命令行参数

#include <stdio.h>
int mian(int argc, char *argv [])    //char *argv [] 和 char **argv 等价
{
	int i;
	
    printf("argc = %d\n", argc);    //argc参数个数
	for (i = 0; i < argc; i++)
    {
        printf("%d: %s \n", i, argv[i]);
    }
}

如果编译上述代码为:a.out可执行文件,并在命令行输入下面命令运行

  • ./a.out REsistance is futile ,那么 argc 为总的字符串数目,为4。argv 为指向char指针的指针,它里面存放的就是这些字符串,argv [0] 是 ./a.out ,以此类推,argv大小为argc
  • 可以用双引号把多个单词括起来形成一个参数,比如 ./a.out "lijia caiji" ruankai

把字符串转换为数字

  1. atoi('3') 把字符串转换为整数值,返回数字3

  2. atof 把字符串转换为double类型的值

  3. atol 把字符串转换为long类型的值

  4. strtol(long)、strtoul(unsigned)、strtod(double),他们都有自动检错功能,他们接受3个参数,

    strtol(const char * str, char ** endptr, int base)
    //str是要转化的字符串,end是要是一个指针的地址,该指针被设置成标识输入数字结束字符的地址,base是以什么进制写入数字。
    char * end;
    strtol(argv[i], &end, 10);   //十进制
    
  5. 如果想把整数或者浮点数转换为字符串,用 sprintf 函数就可以

他们都在stdlib.h头文件中(除了sprintf)

程序

  1. //循环输入字符串
    puts("Please enter a string, enter blank lines to exit");
    while (s_gets(str1, Size1) != NULL && str1[0] != '\0')
    {
        ……
    	puts("enter again:");
    }
    puts("Done!");
    
  2. //用strcmp函数可以检查程序是否要退出
    while (i < Row && s_gets(str[i], Col) != NULL && strcmp(str[i], STOP) != 0 && str[i][0] != '\0')
      	i++;
    //只要用户输入十个字符串或者输入EOF或者输入STOP或者输入空行都可以结束循环
    

第十二章

  1. auto:主要是为了明确表达要使用与外部变量同名的局部变量的意图
  2. register :把变量归为寄存器存储类型,请求最快速度访问该变量。同时,还保护了该变量的地址不被获取
  3. static 具有静态储存器,如果static用于文件作用域声明,则作用域受限该文件,如果用于块作用域声明,则作用域受限于该块
  4. extern: 表明变量定义在别处。

一、存储类别

1、作用域
  1. 块作用域
    • 作用域只在变量定义所在的块存在,离开立马释放内存
    • 内层块中声明变量和外层块中声明的变量同名时,内层会隐藏外层块的定义,离开内层块后内层块中的变量被释放,外层块变量作用域回到原来的作用域。
  2. 函数作用域
  3. 函数原型作用域
  4. 文件作用域(即全局变量)
2、链接
  1. 无连接
    • 块作用域
    • 函数作用域
    • 函数原型作用域
  2. 外部链接
    • 文件作用域
  3. 内部链接(static) static int dodgers = 3
    • 文件作用域
3、存储期
  1. 静态存储期

    • 文件作用域变量都是
  2. 线程存储期

  3. 自动存储期

    • 块作用域

    static int ct = 0; 块作用域加上关键字 static 就能让块作用域具有静态存储期,但是作用域仍然只是变量定义所在的块

    statoc 也可以修饰函数,在函数声明处,但是此时该函数就只能在本文件调用,不加修饰词就是全局调用

  4. 动态分配(malloc)

4、寄存器变量
  1. 与普通变量相比,访问和处理这些变量的速度更快
  2. 存储在寄存器中,所以无法获取寄存器变量的地址
  3. 大多数寄存器变量和自动变量一样,块作用域、无链接、自动存储期
  4. 使用存储类别说明符 register 便可声明寄存器变量
  5. register 是一种请求,编译器可能会忽略你的请求,这时候寄存器变量就变成自动变量,即使是这样,仍然不能对变量使用地址运算符
5、块作用域的静态变量
  1. 可以用 static 创建局部变量,它具有静态存储期、块作用域和局部变量

    for (int count = 0; count <= 3; count++)
    {
        printf("count = %d\n", count);
        func();
    }
    void fun(void)
    {
        int fade = 1;
        static int stay = 1;
        printf("fade = %d, stay = %d\n", fade, stay);
    }
    //fade变量每次执行都会被初始化为1,但是stay变量只有在编译函数的时候才会被初始化一次,所以每次stay变量都会递增1.
    
  2. 不能在函数的形参中使用 static 。但是static可以修饰函数

6、外部链接的静态变量
  1. int Errupt;
    double Up[100];
    extern char Coal;  //如果Coal被定义在另一个文件中,则必须这样声明
    extern char array[][LEN];  // 这样声明一个二维数组
    extern struct list_t list; // 这样声明一个外部的结构体
    void next(void);
    int main(void)
    {
        extern int Errupt;    //此处声明可以省略,但是这样可以增加阅读性,让人知道你需要使用这个变量
        extern double Up[];  //此处声明和上面一样可以省略,此处声明可以不用标明数组大小,因为前面已经定                            义过了
                            //这两处的extern不可省略,一旦省略就相当于重新声明了一个Errupt变量,他会隐藏最开始的变量
        ……
    }
    void next(void)
    {
        ……
    }
    
   
2. 初始化外部变量

   - 和自动变量类似,可以被显式初始化,但是不同的是,只能用常量表达式初始化文件作用域变量,如果没有初始化外部变量,他们会自动初始化为0,static定义的变量也一样

     ```c
     int x = 10;
     int x2 = 2 * x;   //不是常量表达式,x是变量

二、随机函数和静态变量

  1. 随机数myrand和mysrand

    //生成随机数的函数
    static unsigned long int next = 1;  //种子,
    
    unsigned int myrand(void)
    {
        //生成伪随机数的魔术公式,改变种子可以生成真正的随机数
        next = next * 1103515245 + 12345;
        return (unsigned int) (next / 65536) % 32768  //产生0到32767之间的随机数
    }
    
    void mysrand(unsigned int seed)
    {
        next = seed;
    }
    
    //驱动随机函数
    #include <stdio.h>
    int main(void)
    {
    	puts("Please enter a number");
        scanf("%u", &seed);
        mysrand(seed);   //改变种子。如果输入的数字不变,那么随机数只是伪随机数
        printf("%d\n", myrand());   //生成不一样的随机数
        
        return 0;
    }
    
  2. 可以用时间来改变种子以达到生成真正的随机数,

    #include <stdio.h>  
    #include <time.h>  //提供time()函数的原型
    #include <stdlib.h>  //提供rand() 和 srand()函数的原型
    int main(void)
    {
    	srand((unsigned int) time(0));  //初始化种子
        
        for (int i = 0; i <  5; i++)
            printf("%d\n", rand());
        
        return 0;
    }
    
  3. rand() 可以打印任意范围的随机数

    //生成n到m范围的随机数,a为n到m中间有多少个数(即a = m - n + 1)
    rand() % a + n;   //n即使是负数也成立
    

四、分配内存:malloc() 和 free(),exit()

两个函数都在stdlib.h中

  1. malloc()函数:该函数接收一个参数,所需的内存字节数,他会分配内存,但是不会为其赋名,所以需要东西来接收,一般都会选择指针来接受,如果分配内存失败,则会返回空指针,malloc函数返回一个void类型的指针,在以前返回的是char类型的,为了更好的移植,一般在前面加上强制类型转化

    //void * malloc(字节)
    double * ptd;
    ptd = (double *) malloc(30 * sizeof(double)); //声明一个内含30个元素的double类型的数组
    //严谨一点应该还有下面这些
    if (ptd == NULL)
    {
        puts("memory allocation failed.");
        exit(EXIT_FAILURE);  //需要头文件stdlib.h
    }
    
    free(ptd);
    ptd = NULL;
    
  2. 可以利用下面来设置变长数组

    double * ptd;
    ptd = (double *) malloc(n * sizeof(double));  //更灵活
    
  3. free()函数,接受一个指针,一般和malloc函数一起使用,不能释放一个内存两次,在free()函数之后需要将ptr再置空 ,即ptr = NULL;如果不将ptr置空的话 ,后面程序如果再通过ptr会访问到已经释放过无效的或者已经被回收再利用的内存, 为保证程序的健壮性, 一般我们都要写ptr = NULL; .

    double * ptd;
    ptd = (double *) malloc(n * sizeof(double))
    //……对ptd进行操作
    free(ptd);  //最后由free来释放ptd内存,这样这块内存可以重复使用
    ptd = NULL;
    

    注意:free函数一般都是释放一个有malloc分配的内存,不能用于释放其他方式分配的内存

  4. exit()函数,表示终止程序,和 return 不一样的是,他是直接退出程序,而 return 表示返回到函数,该函数的原型在 stdlib.h 头文件中

    //exit函数里面有一个参数,可以填写EXIT_FAILURE
    //EXIT_SUCCESS(或者,相当于0)
    //这两个宏定义都在stdlib.h头文件中
    
    
  5. calloc()函数,也用来分配内存,在以前也是返回char类型的指针,现在也是返回void类型的指针,如果分配内存失败,它返回的是空指针NULL,它也可以用free来释放分配的内存,与malloc不同的是它会清零里面的内容

    // void * calloc(unsigned int, 字节)
    long * ptl;
    ptl = (long *)calloc(100, sizeof (long)); //创建100个8个字节的存储单元,long为8个字节,之所以写sizeof(long)不写4是为了提高代码可移植性
    
  6. 动态内存分配和变长数组

    • 对多维数组,用变长数组更方便,也可以用malloc创建二维数组,如果编译器不支持变长数组特性,就只能固定二维数组的维度,例如

      int n = 5, m = 6;
      int ar2[n][m];
      int (* p2) [6];   //不允许用变长数组时
      p2 = (int (*) [6]) malloc(n * 6 * sizeof(int));  //n * 6数组
      //允许用变长数组时
      int (* p3) [m];
      p3 = (int (*) [m]) malloc(n * m * sizeof(int));  //支持变长数组
      

五、const 类型限定符

  1. 他限定值不能修改,它还可以初始化变量,也可以来修饰数组

  2. 在指针中三种const 用法

    const float * pf  //该指针指向的数值不能修改
    float const * pfc  //表明该指针指向的地址不能修改,不能再指向其他的地址
    const float * const ptr;  //表明该指针既不能指向别处,他所指向的值也不能修改
    

六、volatile 类型限定符

  1. volatile 限定符告诉计算机,代理可以改变该变量的值

  2. 使用

    volatile int locl;
    volatile int * ploc;
    
  3. 可以同时使用const 和 volatile限定一个值,他们的顺序没有影响

    volatile const int loc;
    const volatile int * ploc;
    

    实例,例如:通常把const把硬件时钟设置为程序不能更改的变量,但是可以通过代理改变,这时用 volatile 。

七、restrict 类型限定符

  1. restrict 关键字允许编译器优化某部分代码以更好的支持计算机。它只能用于指针,表明该指针是范文数据对象的唯一初始方式

  2. restrict 还可以用于函数形参中的指针,这意味着编译器可以假定在函数体内其他标识符不会修改该指针指向的数据,而且编译器可以尝试对其优化,使其不做别的用途

    • 例如C库中有两个函数用于把一个位置上的字节拷贝到另一个位置上

      void * memcpy(void * restrict s1, const void * restrict s2, size_t n);
      void *memmove(void * s1, const void * s2, size_t n);
      //这两个函数都从位置 s2 把 n 字节拷贝到位置 s1 上,但是 restrict 修饰的两个指针说明,他们两个肯定不是指向同一个位置的指针,所以可以以此来说明该函数的两个参数位置不能重叠
      

八、_Atomic 类型限定符

九、旧关键字的新位置

void ofmouth(int * const a1, int * restrict a2, int n);  //以前的风格
void ofmouth(int a1[const], int a2[restrict], int n);  //c99允许
  1. static 关键字有些特殊

第13章

二、标准I/O

  • 如果只用一个流指针来读写的话,要注意光标的位置,写完后要把光标移到开头才能读
[1] exit 和 return 的区别
  • 他们两个在最初调用的 main 函数中,效果相同
  • 如果 main 在一个递归程序中,exit( ) 仍然会终止程序,当时return 只会把控制权交给上一级,直至最初一级,然后return结束程序
  • 即使在其他函数中调用 exit 也能结束程序。
[2] fopen函数

该函数的声明在 stdio.h 头文件中,他接受两个参数,第一个是待打开文件的名称,第二个参数是一个字符串,指定待打开的模式,有以下一些模式,他返回的是一个文件指针,他的类型是指向一个FILE的指针,FILE是一个定义在 stdio.h 中的派生类型,当文件打开失败则返回NULL指针

FILE *fp;
fp = fopen("filename", "r"); //以只读的模式打开filename文件
if (fp == NULL)
{
    fprintf(stderr, "Failed to open file.\n");
    exit(EXIT_FAILURE);
}
模式字符串 含义
"r" 以只读方式打开文件不改变文件内容,如果文件不存在就会报错
"w" 以写入模式打开文件,如果文件存在就清空文件,如果不存在就创建一个新文件
"a" 以追加方式打开文件,在现有文件末尾添加内容,如果文件不存在就创建一个新文件
"r+" 以读写方式打开文件,如果不存在就会报错,他会从头开始写入,回覆盖原来的内容,要配合随机访问函数fseek
"w+" 以读写方式打开文件,如果文件存在,就清空文件,如果不存在就创建一个新文件
"a+" 以读写方式打开文件,在现有文件的末尾添加内容,如果文件不存在则创建一个新文件
"rb"、 "wb"、"ab"、"rb+"、"r+b"、 "wb+"、"w+b"、"ab+"、"a+b" 与上一个模式类似,但是以二进制模式而不是文本模式打开文件
"wx"、"wbx"、"w+x"、"wb+x"、"w+bx" 类似于非"x"模式,但是如果文件已存在或以独占模式打开文件,则打开文件失败
  • 使用 "w" 模式的时候最好和 "x" 结合使用,防止 "w" 模式直接清空里面原有的内容
[3] getc() 和 putc()函数
//他们两个类似与getchar 和 putchar ,只不过不同的是他们两个要指明从哪里获取字符
ch = getc(fp)  //从fp指定的文件中获取一个字符
putc(ch, fpout)  //把字符 ch 放入FILE指针fpout指定的文件中
    
//stdout作为与标准输出相关联的文件指针,他定义在stdio.h中,所以putc(ch, stdout)和putchar(ch) 作用相同
[3,2]文件结尾
int ch;
FILE * fp;
fp = fopen("test", "r");
while ((ch = getc(fp)) != EOF)
    putc(ch, stdout);
[4] fclose() 函数
  1. fclose(fp) 函数可以关闭 fp 指针的文件,必要时刷新缓冲区,如果成功关闭,该函数返回0,否则返回 EOF

  2. 一般用法如下

    if (fclose(fp) != 0)
        printf("error in closing file %s\n", argv[1])
    
[5] 指向标准文件的指针
标准文件 文件指针 通常使用的设备
标准输入 stdin 键盘
标准输出 stdout 显示器
标准错误 stderr 显示器

三、文件I/O

[1] fprint 函数和 fscanf 函数
  1. 他们的工作方式和 printf 和 scanf 类似,格式和 getc 和 putc类似,他们需要接受一个 FILE 类型的指针

  2. 示例

    char word[40]
    fprintf(fp, "ruankai");
    fprintf(fp, "li jia is %s", "big cai ji");
    fscanf(stdin, "%39s", words);  //接受标准输入39宽度的字符串,然后赋给words数组
    fscanf(fp, "%lf", &n);   //从fp指向的文件中读取一个double 类型的值赋给n
    
[2] rewind() 函数
  1. 他接受一个FILE类型的指针,作用是返回到文件开始处。
#include <stdio.h>
#include <stdlib.h>
#define MAX 40

int main(int argc, const char *argv[])
{
	FILE *fp;
	char words[MAX];

	if ((fp = fopen("wordy", "a+")) == NULL)
	{
		fprintf(stdin, "Can't open \"wordy\" file.\n");
		exit(EXIT_FAILURE);
	}

	puts("Enter words to add to the file; press the #");//输入单词,在一行的首位输入#退出
	puts("key at the beginning of a line to termiante.");
	while ((fscanf(stdin, "%39s", words) == 1) && (words[0] != '#')) //类似于scanf 非空白开始到空白字符
		fprintf(fp, "%s\n", words);
	puts("file contents:");
	rewind(fp);   //回到文件首部
	while (fscanf(fp, "%s", words) == 1)
		puts(words);
	puts("Done!");
	if (fclose(fp) != 0)
		fprintf(stderr, "error closing file\n");

    return 0;
}
[3] fgets() 和 fputs() 函数

fgets(char *, int, FILE *)

fputs(char *, FILE *)

  1. char buf[SIZE];
    
    fgets(buf, SIZE, fp);  //从fp指向的文件中读取,然后把字符串存入数组buf中
    fputs(buf, fp);  //把buf数组里面的字符串显示到fp指向的文件中
    

五、随机访问

[1] fseek()
  1. 他接受3个参数,第一个是FILE *指针,要查找的文件,第二个参数是偏移量(相对于起始点),必须为 long 类型的值,可以向前移(正)、后移(负)或者不移动,第三个参数为模式,用该参数确定起点,在stdio中规定以下的模式

    模式 偏移量的起始点 旧的实现中
    SEEK_SET 文件开始处 0L
    SEEK_CUR 当前位置 1L
    SEEK_END 文件末尾 2L
  2. 示例

    fseek(fp, 0L, SEEK_SET);  //定位至文件开始处
    fseek(fp, 10L, SEEK_SET);  ////定位至文件中的第10个字节
    fseek(fp, -2L, SEEK_CUR);  ///从文件当前位置前移2个字节
    
  3. 返回值

    • 如果一切正常,fseek() 函数返回0,如果错误则返回 -1
[2] ftell()
  1. 该函数返回 long 类型的值,他返回的是参数指向文件的当前位置距文件开始处的字节数,他定义在 stdio.h 头文件中,他适合以二进制模式打开的文件

  2. 示例

    long last;
    last = ftelll(fp);  ///把文件开始处到fp位置的字节数赋给last
    
  3. 对于文本模式,ftell()返回的值可以作为fseek()的第二个参数,而对于MS-DOS,ftell返回值把 \r\n当作一个字节计数

#include <stdio.h>
#include <stdlib.h>
#define SIZE 81
#define CNTL_Z '\032'  //MS-DOS文本文件中的文件结尾标记

int main(int argc, const char *argv[])
{
	char file[SIZE];
	char ch;
	FILE * fp;
	long count, last;

	puts("Please enter a file to be processed");
	scanf("%80s", file);
	if ((fp = fopen(file, "rb")) == NULL)
	{
		fprintf(stderr, "reverse can't open %s\n", file);
		exit(EXIT_FAILURE);
	}
	fseek(fp, 0L, SEEK_END);
	last = ftell(fp);
    //倒序打印文本里面的内容
	for (count = 1L; count <= last; count++)
	{
		fseek(fp, -count, SEEK_END);    //回退
		ch = getc(fp);
		if (ch != CNTL_Z && ch != '\r')
			putchar(ch);
	}
	putchar('\n');
	if (fclose(fp) != 0)
		fprintf(stderr, "error in closing file %s\n", argv[1]);  //MS-DOS文件

    return 0;
}
[3] fgetpos() 和 fsetpos() 函数

七、其他标准I/O函数

[1] int ungetc(int c, FILE *fp)
  1. ungetc 函数把 C 指定的字符返回到输入流中,如果把一个字符放回输入流中,下次再调用标准输入函数时将读取该字符,每次只会放回一个字符,如果实现允许把一行中的多个字符放回到输入流中,那么下一次输入函数读入的字符顺序与放回时的顺序相反
[2] int fflush(FILE *fp)
  1. 调用 fflush 函数会刷新缓冲区,它会引起输出缓冲区中所有的未写入数据被发送到 fp 指定的输出文件。在输入流中使用 fflush 函数是未定义的,只要最近的一次操作不是输入操作,就可以用该函数来更新流
[3] setvbuf 函数
  • int setvbuf(FILE * restrict fp, char * restrict buf, int mode, size_t size);
  1. setvbuf() 函数创建一个供标准I/O函数替换使用的缓冲区,在打开文件后未对流进行其他操作之前,调用该函数。指针 fp 识别待处理的流,buf 指向带使用的储存区。如果 buf的值不是NULL,则必须创建一个缓冲区,例如,声明一个1024个字符的数组,并传递该数组的地址。然而,如果把NULL作为 buf 的值,该函数会为自己分配一个缓冲区

  2. 变量 size 告诉 setvbuf() 数组的大小

  3. mode 的选择如下:

    mode 含义
    _IOFBF 完全缓冲(在缓冲区满时刷新)
    _IOLBF 行缓冲(在缓冲区满时或者写入一个换行符时)
    _IONBF 无缓冲
  4. 如果操作成功,该函数返回0,否则返回一个非零值

[4] 二进制I/O:fread() 和 fwrite()
  1. 他们以二进制形式处理数据
[5] fwrite
  • size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * fp)
  1. fwrite 函数把二进制数据写入文件,指针 ptr 时待写入数据块的地址,size表示待写入数据块的大小,nmemb表示待写入数据块的数量,fp 指定待写入的文件

  2. 示例

    //保存一个256字节的数据对象(如数组)
    char buffer[256];
    fwrite(buffer, 256, 1, fp);
    //以上调用把一块256字节的数据从buffer写入文件
    
    //保存一个内含10个double 类型值的数组
    double arr[10];
    fwrite(arr, sizeof(double), 10, fp);
    
  3. 它的返回值为成功写入项的数量,正常情况下,返回值就是 nmemb,当如果出现错误,返回值就会小于 nmemb

[6] fread 函数
  • size_t fread(void * ptr, size_t size, size_t nmemb, FILE * fp);
  1. 类似于 fwrite 函数,ptr 是带读取文件数据在内存中的地址,fp指定带读取的文件,该函数用于被 fwrite 写入文件的数据

  2. 示例

    //要恢复上例中arr中10个double 类型值的数组
    double arr[10];
    fread(arr, sizeof(double), 10, fp);
    //该调用把10个double大小的值拷贝进数组中
    
  3. fread() 函数返回成功读取项的数量,正常情况下返回值就是 nmemb

注:在使用fwrite函数后再使用fread函数需要把文件指针重新移动到文件头

//把一个数组里面的值用fwrite函数读入文件内,然后再用fread函数把文件内的值读入到另一个数组内
#include <stdio.h>
#include <stdlib.h>
#define SIZE 10

int main(int argc, const char *argv[])
{
	double arr[SIZE];
	double array[SIZE] = {0};
	FILE *fp = NULL;

	if ((fp = fopen("test", "a+")) == NULL)
	{
		fprintf(stderr, "failed to open file\n");
		exit(EXIT_FAILURE);
	}
	for (int i = 0; i < SIZE; i++)
	{
		arr[i] = i / (double) 2;
		printf("arr[%d] = %.2lf\n", i, arr[i]);
	}
	if ((fwrite(arr, sizeof (double), SIZE, fp)) != 10)  //这个时候fp已经移动了
	{
		fprintf(stderr, "error write file\n");
		exit(EXIT_FAILURE);
	}
	rewind(fp);  ///////重新让fp指向文件头
	if ((fread(array, sizeof (double), SIZE, fp)) == 0)
	{
		fprintf(stderr, "error read file\n");
		exit(EXIT_FAILURE);
	}
	puts("---------------");
	for (int i = 0; i < 10; i++)
	{
		printf("array[%d] = %lf\n", i, array[i]);
	}
	puts("---------------");
	if (fclose(fp) != 0)
		fprintf(stderr, "error closing file\n");

    return 0;
}
[7] feof 和 ferror
  • int feof(FILE *fp)int ferror(FILE *fp)
  1. 当上一次输入调用检测到文件结尾时,feof 返回一个非零值,否则返回0

  2. 当读或者写出现错误,ferror 函数会返回一个非零值,否则返回0

  3. 示例

    FILE *fp;
    if (ferror(fp) != 0)
        fprintf(stderr,"error in reading file.\n");
    
[8] 案例13.5

前面几个函数的应用

//该程序把一系列源文件中的内容附加到另一个文件中
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define BUFSIZE 4096
#define SIZE 81
bool bl = false;
char *s_gets(char *, int);
void append(FILE *, FILE *);
int main(int argc, const char *argv[])
{
	FILE *fa, *fs;
	int files = 0;
	char file_app[SIZE];
	char file_src[SIZE];
	int ch;

	puts("Enter name of destination file:");
	s_gets(file_app, SIZE);
	if ((fa = fopen(file_app, "a+")) == NULL)
	{
		fprintf(stderr, "can't open %s\n", file_app);
		exit(EXIT_FAILURE);
	}
	if (setvbuf(fa, NULL, _IOFBF, BUFSIZE) != 0)
	{
		fputs("can't create output buffer\n", stderr);
		exit(EXIT_FAILURE);
	}
	puts("Enter name of first source file (empty line to quit): ");
	while (s_gets(file_src, SIZE) && file_src[0] != '\0')
	{
		if (strcmp(file_src, file_app) == 0)  //如果名字相同就说明是同一个文件
			fputs("Can't append file to itself, enter again\n", stderr);
		else if ((fs = fopen(file_src, "r")) == NULL)  //打开失败
			fprintf(stderr, "Can‘t open %s file, enter agagin.\n", file_src);
		else if (setvbuf(fs, NULL, _IOFBF, BUFSIZE) != 0)  //创建缓冲区失败
		{
			fputs("can't create input buffer, enter again.\n", stderr);
			continue;	
		}
		else
		{
			append(fs, fa);
			if (ferror(fs) != 0)  ///读写失败
			{
				fprintf(stderr, "Enter in reading file %s, enter again\n", file_src);
				continue;
			}
			if (ferror(fs) != 0)  //读写失败 
			{
				fprintf(stderr, "Enter in writing file %s. enter again\n", file_app);
				continue;
			}
			if (fclose(fs) != 0)
				fprintf(stderr, "error in closing %s file\n", file_src);
			files++;
			printf("File %s appended.\n", file_src);
			puts("Next file (empty line to quit): ");
		}
	}
	printf("Done appending. %d files appended.\n", files);
	rewind(fa);
	printf("%s contents: \n", file_app);
	while ((ch = getc(fa)) != EOF)
		putchar(ch);
	puts("Done displaying.");
	if (fclose(fa) != 0)
		fprintf(stderr, "error in closing %s file.\n", file_app);

    return 0;
}

void append(FILE *source, FILE *dest)
{
	size_t bytes;
	static char temp[BUFSIZE];  //不用每次调用函数都创建一个数组,这样只有在编译的时候才会创建
	
    //这里用循环可以让他一直读然后一直写,一直读到文件结尾,如果文件内容超过4096个字节,使用循环仍然可以使用,但是如果不用循环,执行一遍的话,他只会读写4096个字节。
	while ((bytes = fread(temp, sizeof(char), BUFSIZE, source)) > 0)
	//bytes = fread(temp, sizeof(char), BUFSIZE, source);
		fwrite(temp, sizeof(char), bytes, dest);
}

char *s_gets(char arr[], int size)
{
	char * output;
	char * find;

	output = fgets(arr, size, stdin);
	if (output)
	{
		find = strchr(arr, '\n');
		if (find)
			*find = '\0';
		else
			while (getchar() != '\n')
				continue;
	}

	return output;
}
[9] 案例13.6
//该程序创建一个储存 double 类型数组的文件,然后让用户访问这些内容
#include <stdio.h>
#include <stdlib.h>
#define ARSIZE 1000

int main(int argc, const char *argv[])
{
	double num[ARSIZE] = {0};
	double value;
	const char * file = "numbers.dat";
	int i;
	long pos;
	FILE *iofile;

	//create an array of type double
	for (i = 0; i < ARSIZE; i++)
		num[i] = 100.0 * i + 1.0 / (i + 1);
	//open file
	if ((iofile = fopen(file, "wb")) == NULL)
	{
		fprintf(stderr, "failed to open file %s in write mode.\n", file);
		exit(EXIT_FAILURE);
	}
	//write the array to the file
	fwrite(num, sizeof(double), ARSIZE, iofile);
	if (fclose(iofile) != 0)
		fprintf(stderr, "error in closing file %s\n", file);
	//open the file again
	if ((iofile = fopen(file, "rb")) == NULL)
	{
		fprintf(stderr, "failed to open file %s in read mode\n", file);
		exit(EXIT_FAILURE);
	}
	//reads the selected content from  the file
	printf("Enter an index in the range 0-%d.\n", ARSIZE - 1);
	while (scanf("%d", &i) == 1 && i >= 0 && i < ARSIZE)
	{
		pos = (long)i * sizeof(double);
		fseek(iofile, pos, SEEK_SET);
		fread(&value, sizeof(double), 1, iofile);
		printf("The value there is %lf.\n", value);
		puts("Next index (out of range to quit): ");
	}
	if (fclose(iofile) != 0)
		fprintf(stderr, "failed to closing file %s in read mode.\n", file);
	puts("Bye!");

    return 0;
}

第十四章 结构体

struct book {
	char title[41];
	char author[31];
	float value;
};  //此处的分号不能省略,他表示结构体的结束
//结构体的声明可以放在函数内部定义,也可以放在函数外部实现
struct book library;  //这里把library声明为一个使用book结构布局的结构变量
[1]声明的简化
  1. struct book {
    	char title[41];
    	char author[31];
    	float value;
    } library;  //声明的右花括号跟变量名、
    
    //即声明结构的过程和定义结构变量的过程可以组合成一个步骤
    struct {  //无结构标记
        char titile[41];
        char author[31];
        float value;
    } library;  //但是这种只能声明一次,如果打算多次使用结构模板,就要使用代表及的形式
    
[2] 初始化结构
  1. //按顺序初始化 (原始初始化器)
    struct book {
    	char title[41];
    	char author[31];
    	float value;
    }; 
    
    //初始化library
    struct book library = {
        "the first string.",  //它们之间用逗号分隔,换行只是提高代码的可读性
        "the second string.",
        3.1415
    };
    
  2. 用点来初始化(指定初始化器)

    struct book surprise = { .value = 3.1415};   //只初始化一个
    
    //可以按随意顺序初始化,中间用逗号隔开
    struct book gift = { .value = 3.1415,   
                         .author = "ruan kai",
                         .titile = "cai ji"};  
    
  3. 可以两种结合使用

    struct book gift = {.value = 18.9,
                        .author = "li jia",
                        0.25};
    //开始是指定初始化,初始化了value 和 author的值,然后再又按顺序初始化了value(因为在定义的结构中 value 在 author 下面定义的)
    
  4. 可以在定义结构的同时定义结构变量

    struct book {
        char title[31];
        char author[31];
        float value;
    } library, * pstr;   //定义了一个struct book结构的变量和一个结构指针
    
[3] 访问结构成员
  1. //用点来访问结构中的成员
    //例如 library.title     library.float
    //library只是一个结构,library.title是一个char类型的数组
    
[4] 结构数组

一、表示结构数组的成员

//把library 声明为一个内含100个元素的数组。数组的每一个元素都是book类型的数组。
struct book library[100];
  1. library 不是结构名,他是一个数组名
  2. library[0] 是一个结构变量名
  3. 访问结构数组的成员
    • library[0].value 第一个数组元素与 value 相关联
    • library[2].title[4] 数组 librar[2] 元素的 title 成员的一个字符
[5] 嵌套结构
struct names {
    char first[20];
    char last[20];
};

struct guy {
	struct names handle;  //这个结构体里面可以嵌套其他的结构体
    char favfood[20];
    float income;
};

//初始化
struct guy fellow = {
    {"ruan", "kai"},  //访问用 fellow.handle.first 和 fellow.handle.last 
    "apple",
    293.12
}
[6] 指向结构的指针

一、访问

  1. 用箭头 -> 来访问

    struct name {
        char first[20];
        char last[20];
    };
    
    struct names name[2]{
        {
            "li",
            "jia"
        },
        {
            "ruan",
        	"kai"
        }
    };
    struct names * him;
    
    him = &name[0];   //也可以写成 him = name
    printf("him->first is %s,  (*him).last is %s.\n", him->first, (*him).first);  //li jia
    him++;  //指针加一移动到下一个结构变量
    printf("%s  ,  %S.\n", him->first, (*him).first);
    
  2. 结构变量名并不是结构的地址,所以要在前面加上取地址符 & ,但是如果是结构数组,数组名就是该数组的地址

  3. 有两种方法访问成员

    • 用 -> 来访问
    • (*him).first == name[0].first == him->first ,此处必须使用括号,因为 . 运算符的优先级比 * 运算符高

二、传递结构

struct book {
	char title[51];
    float value1;
    float value2;
};
struct book library;
float function()
  1. 传递结构成员

    float function(float x1, float x2)
    fucntion(library.value1, library.value2); //函数只关心传入的数据
    
  2. 传递结构地址

    function(&library);  //然后就可以用 -> 访问结构里面的任意一个成员
    float function(struct book * pstr)
    {
        return (pstr->value1 + pstr->value2);
    }
    
  3. 传递结构23

    function(struct book library);
    ....
    void function(struct book structure)
    {
        return structure.value;
    }//把library结构的数据复制给structure(不影响原始数据),然后就可以用structure来使用library结构的成员
    

三、其他结构特性

  1. 如果两个结构变量的结构类型是一样的,那么可以直接把一个结构赋值给另一个结构

    a_struct = b_struct (这两个都是相同的结构类型 struct A)

  2. 对于函数既可以传递结构指针,返回结构指针,当然现在也可以传递结构,返回结构本身

    • 传递结构指针(优点:方便快捷,但是他会改动原始数据,不安全,可以加上const限定符)

      struct name {
          char fname[30];
          char lname[30];
          int letters;
      };
      
      viod getstruct(struct name *);
      
      int main(void)
      {
      	struct name person;
          
          getstruct(&person);   //传递指针,即结构的地址
      }
      void getstruct(struct name * pstr)
      {
          puts("Now enter the first name.");
          s_gets(pstr->fname, 30);
          puts("Now enter the last name.");
          s_gets(pstr->lname, 30);
          letter = strlen(pstr->fname) + strlen(pstr->lname);
      }
      
    • 传递结构本身

      struct name {
          char fname[30];
          char lname[30];
          int letters;
      };
      
      struct name getstruct(void)
      int main(void)
      {
      	struct name person;
          getstruct(&person);   //传递指针,即结构的地址
      }
      void getstruct(struct name * pstr)
      {
          puts("Now enter the first name.");
          s_gets(pstr->fname, 30);
          puts("Now enter the last name.");
          s_gets(pstr->lname, 30);
          letter = strlen(pstr->fname) + strlen(pstr->lname);
      }
      

      注:在函数声明的时候可以省略 void function(sturct name);

  3. 传递指针的时候可以结合 malloc 函数来使用

    struct name {
    	char * fname;            //结构里面装了一个地址
        char * lname;    //地址
        int letters;   
    };  ///该结构装了两个 char 类型的地址,然后还有一个 int 类型的变量
    //如果直接用不初始化的两个 char 类型的指针的时候,它可能会随机指向一个地址,野指针
    int main(void)
    {
    	struct name person;
        char temp[30];
        
        puts("Please enter you first name.");
        s_gets(temp, 30);
        person.fname = (char *) malloc(strlen(temp) + 1);
        strcpy(person.fname, temp);
        
        return 0;
    }
    
[7] 复合字面量和结构
  1. 语法

    • 把类型名放在圆括号中,后面紧跟一个用花括号括起来的初始化列表
  2. 用法

    • 可以把复合字面量作为函数的参数

      struct rect {
      	double x;
          double y;
      };
      
      double rect_function(struct rect r) {return r.x * r.y;}
      
      int main(void)
      {
          double sum;
          
          sum = rect_function((struct rect) {10.5, 20.0});
      }
      
    • 也可以传递给复合字面量指针

      struct rect {
          double x;
          double y;
      };
      
      double rect_function(struct rect * rp) {return rp->x * rp->y;}
      int main(void)
      {
          double sum;
          
          sum = rect_function(&(struct rect) (10.5, 20.0))
      }
      
[8] 伸缩型数组

一、声明规则

  1. 伸缩型数组成员必须是结构的最后一个成员
  2. 结构中必须至少有一个成员
  3. 伸缩数组的声明类似于普通数组,只是它的方括号中是空的

二、示例、

#include <stdio.h>
#include <stdlib.h>

#define FLEXLEN 5  //FLEXLEN 为你想设置 score数组的内存
struct flex {
	size_t count;
	double average;
	double score [];   //伸缩型数组成员,
};

void showFlex(struct flex *);

int main(int argc, const char *argv[])
{
	struct flex * pstr1;
	int i;
	double tot = 0;

	pstr1 = (struct flex *) malloc(sizeof(struct flex) + FLEXLEN * sizeof(double));  // 用 malloc 函数来分配你想要的空间
	pstr1->count = FLEXLEN;
	for (i = 0;i < FLEXLEN;;i++)
	{
		pstr1->score[i] = 20.0 - i;
		tot += pstr1->score[i];
	}
	pstr1->average = tot / FLEXLEN;;
	showFlex(pstr1);
	free(pstr1);
	pstr1 = NULL;

	return 0;  
}

void showFlex(struct flex * pstr)
{
	int i;

	printf("Score : ");
	for (i = 0; i < pstr->count; i++)
		printf("%g ", pstr->score[i]);
	printf("\nAverage: %g\n", pstr->average);
}

三、注意事项

  1. 不能用结构进行赋值或者拷贝

    • struct flex * pstr1, * pstr2;
      //当pstr1 和 pstr2 都用malloc申请内存后才能进行如下赋值
      *pstr1 = *pstr2;   //这样只能拷贝除伸缩型数组成员以外的其他成员,如果要拷贝所有数据,应使用 memcpy 函数
      
  2. 不要以按值的方式把这种结构传递给结构

  3. 不要使用带伸缩型数组成员的结构作为数组成员或另一个结构的成员

[9] 匿名结构
  1. 用于简化嵌套结构

  2. 示例

    struct names {
        char first[20];
        char last[20];
    };
    
    struct person {
        int id;
        struct names name; //正常的嵌套结构体
    }
    
    //可以使用如下方法简化
    struct person {
        int if;
        struct {char first[20]; char last[20];};  //访问数组成员要简单一点
    };
    //两种方式初始化都是一样的
    struct person ted = {8483, {"Ted", "Grass"}};
    
[10] 把结构内容保存到文件中

一、程序14.14

  • #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAXTIT 40
    #define MAXAUTH 40
    #define MAXBK 10
    struct book {
    	char title[MAXTIT];
    	char author[MAXAUTH];
    	float value;
    };
    
    char *s_gets(char *, int);
    float getValue(void);
    
    int main(int argc, const char *argv[])
    {
    	struct book library[MAXBK];
    	int count = 0;
    	int index, filecount;
    	FILE * pbooks;
    	int size = sizeof(struct book);
    
    	if ((pbooks = fopen("book.dat", "a+b")) == NULL)
    	{
    		fputs("failed to open file book.dat.\n", stderr);
    		exit(EXIT_FAILURE);
    	}
    
    	rewind(pbooks);  //定位到文件开始
        //如果book.dat文件内本来就有大于结构体struct book的字节数,那么fread就会返回1,此时就打印里面的内容,即有东西就打印原来的内容
    	while (count < MAXBK && fread(&library[count], size, 1, pbooks) == 1)
    	{
    		if (count == 0)
    			puts("current contents of book.dat: ");
    		printf("%s by %s: $%.2f\n", library[count].title, 
    				library[count].author, library[count].value);
    		count++;
    	}
    	filecount = count;   //记录从原来文件内容添加了几个结构的数量
    	if (count == MAXBK)  //如果该条件成立,就证明结构体已经装满了,装不下了后面输入的内容
        {
    		fputs("The book.dat file is full.", stderr);
    		exit(EXIT_FAILURE);
    	}
    
    	puts("Please add new book titles.");
    	puts("Press [enter] at the start of a line to stop.");
    	while (count < MAXBK && s_gets(library[count].title, MAXTIT) && library[count].title[0] != '\0') //开头输入空行结束
    	{
    		puts("Now enter the author.");
    		s_gets(library[count].author, MAXAUTH);
    		puts("Now enter the value.");
    		library[count++].value = getValue();
    		if (count < MAXBK)
    			puts("enter the next file.");
    	}
    
    	if (count > 0)
    	{
    		puts("Here is the list of your books: ");
    		for (index = 0; index < count; index++)
    			printf("%s by %s: $%.2f\n", library[index].title,
    					library[index].author, library[index].value);
            //写入文件的应该只是后面添加的内容,而不是前面文件中已有的内容 
    		fwrite(&library[filecount], size, count - filecount, pbooks);
    	}
    	else
    		puts("No books? Too bad.");
    
    	if (fclose(pbooks) != 0)
    		fputs("Error in closing book.dat file.", stderr);
    	puts("Bye.");
    
        return 0;
    }
    
    char *s_gets(char * str, int size)
    {
    	char * export, * find;
    
    	export = fgets(str, size, stdin);
    	if (export)
    	{
    		find = strchr(str, '\n');
    		if (find)
    			*find = '\0';
    		else
    			while (getchar() != '\0')
    				continue;
    	}
    
    	return export;
    }
    
    float getValue(void)
    {
    	float export;
    	int status;
    
    	while (!((status = scanf("%f", &export)) == 1 && export > 0))
    	{
    		if (status == 0)
    			scanf("%*s");
    		puts("enter error, Please enter again.");
    	}
    	while (getchar() != '\n')
    		continue;
    
    	return export;
    }
    
[11] 联合体

一、声明

  1. union hold {
        int digit;
        double bigfl;
        char letter
    };//它只能储存一个 int 类型或者一个 double 类型或者一个 char 类型的值
    
  2. 联合体只能存储一个值

二、初始化

  1. union hold vala;
    vala.letter = 'R';
    union hold valb = vala;     //用另一个联合来初始化
    union hold valc = {88};     // 初始化联合的digit成员
    union hold vald = {.bigfl = 118.2}  //指定初始化
    

三、匿名联合

为了简化嵌套

  1. struct owner {
    	char security[12];
    	……
    };
    
    struct lease {
    	char name[40];
    	char headquarters[40];
    	……
    };
    
    union data {
        struct owner owncar;
        struct lease leasecar;
    };
    
    //正常嵌套
    struct car_data {
    	char make[15];
        int status;
        union data ownerinfo;
        ……
    };
    
    //使用匿名结构
    struct car_data {
        char make[15];
        int status;
        union {
            struct owner owncar;
            struct lease leasecar;
        };
        ……
    };
    
[12] 枚举类型

枚举只是为了提高代码的可读性和可维护性

一、声明

  1. 使用 enum 关键字来创建一个新的类型,然后使用这个类型来定义这种结构的枚举类型

    enum color {red, orange, yellow, green, blue, violet}; //声明一个枚举类型
    enum color color1;  //定义一个 enum color 类型的变量 color1 ,color1的值可能是上面元素中其中的一个
    
  2. color 的可能值被称为枚举符,即枚举类型中的元素,枚举符是 int 类型的,但是枚举变量 color1 可以是其他类型的,前提是该整数类型可以存储枚举常量,例如:char、unsigned char 等等

  3. 默认情况下,枚举列表中的常量都被赋予0、1、2等,即后面的值是前一个值加一,所以当你指定赋值的时候,后面的值也会跟着改变

  4. 示例

    • 程序清单14.15

      #include <stdio.h>
      #include <stdbool.h>
      #include <string.h>
      
      #define LEN 30
      enum spectrum {red, orange, yellow, green, blue, violet};
      const char * colors [] = {"red", "orange", "yellow", "green", "blue", "violet"};
      char *s_gets(char *, int);
      
      int main(int argc, const char *argv[])
      {
      	char choice[LEN];
      	enum spectrum color;
      	bool color_found = false;
      
      	puts("Enter a color (empty line to quit): ");
      	while (s_gets(choice, LEN) && choice[0] != '\0')
      	{
      		for (color = red; color <= violet; color++)
      			if (strcmp(choice, colors[color]) == 0) //colors数组是函数开始定义的
      			{
      				color_found = true;
      				break;
      			}
      		if (color_found)
      		{
      			switch (color)
      			{
      				case red:    puts("rosess are red.");
      							 break;
      				case orange: puts("Poppies are orange.");
      							 break;
      				case yellow: puts("Sunflowers are yellow.");
      							 break;
      				case green:  puts("Grass is green.");
      							 break;
      				case blue:   puts("bluebells are blue.");
      							 break;
      				case violet: puts("Violets are violet");
      							 break;
      			}
      		}
      		else
      			 printf("I don't know about the color %s.\n", choice);
      		color_found = false;
      		puts("Next color, please (empty line to quit):");
      	}
      	puts("Goodbye!");
      
          return 0;
      }
      
      char *s_gets(char * ptr, int size)
      {
      	char * export, * find;
      
      	export = fgets(ptr, size, stdin);
      	if (export)
      	{
      		find = strchr(ptr, '\n');
      		if (find)
      			*find = '\0';
      		else
      			while (getchar() != '\n')
      				continue;
      	}
      
      	return export;
      }
      

注意:结构体,联合体,枚举类型的定义的标记和变量都不能相同

[13] typedef

一、typedef 和 define 的不同之处

  1. typedef 创建的符号名只受限于类型,不能用于值
  2. typedef 由编译器解释,不是预处理器
  3. 在其受限范围内, typedef 比 define 更灵活

二、示例

  1. typedef char * STRING;   // STRING就相当于 char * 类型
    STRING name, sign;
    
    //typedef 还可以用于结构
    typedef struct complex {
        float real;
        float imag;
    } COMPLEX;   //COMPLEX 就相当于struct complex 类型
    
    //typedef来命名一个结构的时候,可以省略该结构的标签
    typedef struct {double x; double y;} rect;
    rect r1 = {3.0, 6.0};  //相当于struct {double x; double y;} r1 = {3.0, 6.0};
    

注意:typedef 能够嵌套使用

  1. typedef int arr[5];   //arr 表示 int 类型
    typedef arr * parr;  // int * 类型
    
[14] 函数和指针

一、声明

  1. 只需要把函数声明中的函数名替换成 (*pf) 就可以创建和函数返回值形参相同的函数指针

    void function(char *);  //函数声明
    void (*pf) (char *);  //函数指针  ,接受一个 char * 类型的变量,返回 void 类型
    
  2. 注意,* 和 pf 一定要有括号

  3. 函数名就是该函数地址

二、案例

程序清单14.16

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define LEN 81

char *s_gets(char *, int);  
char showmenu(void);     //展示菜单,获取选择 choice
void eatline(void);   // 清理该行多余的输入
void mytoupper(char *);  //转换为大写
void mytolower(char *);  //转换为小写
void transpose(char *);  //大写变成小写,小写变成大写
void origin(char *);   //不做任何改变
void show(void (*)(char *), char *);    //展示

int main(int argc, const char *argv[])
{
	char line[LEN];
	char copy[LEN];
	char choice;
	void (*pfun) (char *);

	puts("Enter a string (empty line to quit): ");
	while (s_gets(line, LEN) != NULL && line[0] != '\0')
	{
		while ((choice = showmenu()) != 'n')
		{
			switch (choice)
			{
				case 'u': pfun = mytoupper;	break;
				case 'l': pfun = mytolower; break;
				case 't': pfun = transpose; break;
				case 'o': pfun = origin; break;
			}
			strcpy(copy, line);
			show(pfun, copy);
		}
		puts("Enter next string.");
	}
	puts("Bye!");

    return 0;
}

char *s_gets(char * str, int size)
{/*{{{*/
	char * export, * find;

	export = fgets(str, size, stdin);
	if (export)
	{
		find = strchr(str, '\n');
		if (find)
			*find = '\0';
		else
			while (getchar() != '\n')
				continue;
	}
	
	return export;
}/*}}}*/

char showmenu(void)
{/*{{{*/
	char ans;

	puts("Enter menu choice:");
	puts("u) uppercase             l) lowercase");
	puts("t) transposed case       o) original case");
	puts("n) next string");
	ans = tolower(getchar());
	eatline();
	while (!strchr("ulton", ans))  //用strchr来检验这些无序的选择
	{
		puts("Please enter a u, l, t, o, or n:");
		ans = tolower(getchar());
		eatline();
	}

	return ans;
}/*}}}*/

void eatline(void)
{/*{{{*/
	while (getchar() != '\n')
		continue;
}/*}}}*/

void mytoupper(char * str)
{/*{{{*/
	while (*str)
	{
		if (*str >= 'a' && *str <= 'z')
			*str = *str - 32;
		str++;
	}
}/*}}}*/

void mytolower(char * str)
{/*{{{*/
	while (*str)
	{
		if (*str >= 'A' && *str <= 'Z')
			*str = *str + 32;
		str++;
	}
}/*}}}*/

void transpose(char * str)
{/*{{{*/
	 while (*str)
	 {
		 if (*str >= 'a' && *str <= 'z')
			 *str = *str - 32;
		 else if (*str >= 'A' && *str <= 'Z')
			 *str = *str + 32;
		 str++;
	 }
}/*}}}*/

void origin(char * str)
{
}

void show(void (*pf)(char *), char * str)
{/*{{{*/
	pf(str);
	puts(str);
}/*}}}*/

注意上述代码中用 strchr 来寻找是否是合理的选项

常用代码

while (count < MAX && s_get(pstr1[count].first, NLEN) != NULL && pstr1[count].first[0] != '\0')  //循环输入,输入空行退出

第十五章

一、按位逻辑运算符

[1] 二进制反码或按位取反:~
  1. 把 1 变成 0,0 变成 1 。

    ~(10011010)
    (01100101)
    
[2] 按位与:& (用作掩码)
  1. 只有都为 1 或者都为真,结果才是 1 或者才是真,有一个是0就必然是0

    (10010011) & (00111101)  
    (00010001)
    
  2. val &= 0377; 等同于 val = val & 0377

  3. 0 & 任意数都为 0,1 & 任意数都为他本身

[3] 按位或:| (用作打开位)
  1. 如果两个运算对象有一个为 1 或者为真,结果就为 1 或者为真

    (10010011) & (00111101) 
    (10111111)
    
  2. val |= 0377; 等同于 val = val | 0377;

  3. 1 | 任意数都为1, 0 | 任意数都为他本身

[4] 按位异或:^
  1. 两个运算对象不一样,结果就为真,1

    (10010011)
    (00111101)
    -----------
    (10101110)
    
  2. val ^= 0377; 等同于 val = val ^ 0377;

  3. 0 ^ b 等于 b 本身,1 ^ b 等于 ~b

[5] 用法
  1. 掩码:flags = flags & (00000010),把想遮住的码位置为1,可以简写
  2. 打开位: flags = flags | (00000010), 把想打开的位置为1,可以简写
  3. 关闭位: flags = flags & ~(00000010),把想清空的位置为1,可以简写
  4. 切换为: flags = flags ^ (00000010),把想切换的位置为1(即反转对应位),可以简写
  5. 检查位:if ((flags & MASK) == MASK) 其中MASK = (00000010)

二、移位运算符

[1] 左移:<<
  1. 将运算符左侧对象每一位都向左移动右侧运算对象指定的位数。

  2. 左侧运算对象左末端位的值丢失,用 0 填充空出的位置

    (10001010) << 2;
    (00101000)
    
  3. 简写 num <<= 2; 等同于 num = num << 2;

[2] 右移:>>
  1. 和左移类似,不同的是填充,因为填充是最高位,涉及符号

  2. 如果是无符号类型,用 0 填充空出的位置

  3. 如果是有符号类型,结构取决于机器,空出的位置可用 0 填充,或者用符号位的副本填充

    (10001010) >> 2;   //对于有符号,
    (00100010);   //在某些系统中的结果,如果上面的表达式时无符号,那么在任何系统都是这个值
    (11100010);  //在另一些系统中,注意这里全部都是补 1
    
  4. 它也可以简写: num >>= 2;

[3] 用法:移位运算符
  1. num << n;    //相当于 num 乘以 2 的 n 次方
    num >> n;    //如果 num 为非负,则相当于 num 除以 2 的 n 次方
    

实例:15.1

//输入一个数字,转换成二进制,存到字符数组中并打印出来
#include <stdio.h>
#include <limits.h>

char *itobs(int, char *);
void showBinstr(char *);

int main(int argc, const char *argv[])
{
	char bin_str[CHAR_BIT * sizeof(int) + 1];  //CHAR_BIT是本系统中1个字节所占的位数,后面加1是因为要多留出来一位放空字符 '\0'
	int num;

	puts("Enter integers and see them in binary.");
	puts("Non-numberic input terminates program.");  //输入非数字退出
	while (scanf("%d", &num) == 1)
	{
		if (num < 0)  //如果输入负数,则重新输入
		{
			puts("Pleas enter an integer greater than 0.");
			continue;
		}
		itobs(num, bin_str);
		printf("%d is:\n", num);
		showBinstr(bin_str);
        puts("Enter next number.");
		puts("Non-numberic input terminates program.");
	}

    return 0;
}

char *itobs(int num, char * str)
{/*{{{*/
	int i;
	const static int size = CHAR_BIT * sizeof(int);

	for (i = size - 1; i >= 0; i--, num >>= 1)
		str[i] = (1 & num) + '0';  //利用掩码,得到最后一位
	str[size] = '\0';

	return str;
}/*}}}*/

void showBinstr(char * str)
{/*{{{*/
	int i = 0;

	while (str[i])
	{
		if (i % 4 == 0 && i = 0) //i 为 0 的时候对4取余等于 0 
			putchar(' ');
		putchar(str[i++]);
	}
	putchar('\n');

}/*}}}*/

变式:

//输入一个数字,打印二进制,并且翻转后面的 BIT 位
#include <stdio.h>
#include <limits.h>

#define BIT 4
char *itobs(int, char *);
void showBinstr(char *);
int invert_end(int, int);

int main(int argc, const char *argv[])
{
	char bin_str[CHAR_BIT * sizeof(int) + 1];
	int num, reverse;

	puts("Enter integers and see them in binary.");
	puts("Non-numberic input terminates program.");
	while (scanf("%d", &num) == 1)
	{
		if (num < 0)
		{
			puts("Pleas enter an integer greater than 0.");
			continue;
		}
		itobs(num, bin_str);
		printf("%d is:\n", num);
		showBinstr(bin_str);
		reverse = invert_end(num, BIT);	  //将num反转后面的 BIT 位
		printf("Inverting the last %d bits giver.\n", BIT);
		showBinstr(itobs(reverse, bin_str));
		puts("Enter next number.");
		puts("Non-numberic input terminates program.");
	}

    return 0;
}

char *itobs(int num, char * str)
{/*{{{*/
	int i;
	const static int size = CHAR_BIT * sizeof(int);

	for (i = size - 1; i >= 0; i--, num >>= 1)
		str[i] = (1 & num) + '0';
	str[size] = '\0';

	return str;
}/*}}}*/

void showBinstr(char * str)
{/*{{{*/
	int i = 0;

	while (str[i])
	{
		if (i % 4 == 0 && i != 0)
			putchar(' ');
		putchar(str[i++]);
	}
	putchar('\n');
}/*}}}*/

int invert_end(int num, int bit)
{/*{{{*/
	int mask = 0;
	int bitval = 1;
    //得到末尾有 bit 个 1 的掩码
	while (bit-- > 0)   //循环bit次
	{
		mask |= bitval;//将mask中对应bitval中的1的位强制置为1,对应bitval的0位不变
		bitval <<= 1; //bitval = 1 、10、100、1000、10000 ……
	}//上面两句结合,mask每次循环完基本都是 1、11、111、1111、11111,循环几次就是几个1,这样就得到了bit位的掩码

	return num ^ mask;  //mask掩码中0位都不变,1位都反转
}/*}}}*/

三、位字段

操控位的第二种方法

[1] 位字段
  1. 声明:

    struct bit {  //可以省略 bit 
        unsigned int autfd: 1; // 1位,只能表示 0 或 1
        unsigned int bldfc: 1;
        unsigned int undln: 1;
        unsigned int itals: 1;
    } prnt;   //声明了一个bit类型的字段 prnt
    
    struct {
        unsigned int code1 : 2;
        unsigned int code2 : 2;
        unsigned int code3 : 8;
    } prcode;  //创建了两个2位的字段和一个8位字段
    
  2. 赋值

    prnt.itals = 0;  //用点赋值
    prnt.undln = 1;
    
  3. 对齐

    struct {
        unsigned int field1 : 1;  //第一位
        unsigned int 		: 2;  //跳过2位,空2位
        unsigned int field2 : 1; //第4位
        unsigned int		: 0;  //unsigned int 类型剩余的位都用0填补
        unsigned int field3 : 1; //在下一个 unsigned int中
    }
    

程序清单 15.4

//详情见书
#include <stdio.h>
#include <stdbool.h>
#include <limits.h>
//边框
#define SOLID 0
#define DOTTED 1
#define DASHED 2
//三原色
#define BLUE 0x4
#define GREEN 0x2
#define RED 0x1
//混合色
#define BLACK 0
#define YELLOW (RED | GREEN)
#define VIOLET (RED | BLUE)
#define CYAN (GREEN | BLUE)
#define WHITE (RED | GREEN | BLUE)
//按位方法中用到的符号常量
#define OPAQUE 0x1
#define FILL_BLUE 0x8
#define FILL_GREEN 0x4
#define FILL_MASK 0xE
#define BORDER 0x100
#define B_RED 0x2
#define B_GREEN 0x4
#define B_BLUE 0x8
#define B_SOLID 0x0
#define B_DOTTED 0x1000
#define B_DASHED 0x2000
#define STYLE_MASK 0x3000
const char * colors[] = {"black", "red", "green", "yellow", 
	                      "blue", "violet", "cyan", "white"};
struct box_props {
	bool opaque				 : 1;
	unsigned int fill_color  : 3;
	unsigned int			 : 4;
	bool border		         : 1;
	unsigned int border_color: 3;
	unsigned int border_style: 2;
	unsigned int			 : 2;
};

union view {
	struct box_props st_view;
	unsigned short us_view;
};

void show_setting(const struct box_props *); //针对位字段
void show_setting1(unsigned short);  //针对按位运算
char *itobs(int , char * );

int main(int argc, const char *argv[])
{
	union view box = {{true, YELLOW, true, BLUE, DASHED} };
	char bin_str[CHAR_BIT * sizeof(unsigned int) + 1];

	puts("Original box setting:");
	show_setting(&box.st_view);
	puts("---------------------");
	puts("Box setting using unsigned int view:");
	show_setting1(box.us_view);

	printf("bits are %s\n", itobs(box.us_view, bin_str));
	box.us_view &= ~0xE;   //把填充色位清零(必须要先清零,防止原先的值造成影响)
	box.us_view |= (B_BLUE | B_GREEN);   //填充颜色,这里没有用到移位,所以要改变
	box.us_view ^= 0x1;  //切换是否透明的位(执行一次就反转一次)
	box.us_view ^= 0x100;  //切换是否显示边框
	box.us_view &= ~STYLE_MASK;  //把style位清零
	box.us_view |= B_DASHED;  //把样式设置成虚线
	printf("\nModified box setting: \n");
	show_setting(&box.st_view);
	printf("\nBox setting using unsigned int view:\n");
	show_setting1(box.us_view);
	printf("bits are %s.\n", itobs(box.us_view, bin_str));

    return 0;
}

void show_setting(const struct box_props * pstr)
{/*{{{*/
	printf("Box is %s.\n", pstr->opaque == true ? "opaque" : "transparent");
	printf("the fill color is %s.\n", colors[pstr->fill_color]);
	printf("Border %s.\n", pstr->border == true ? "shown" : "not shown");
	printf("The border color is %s.\n", colors[pstr->border_color]);
	printf("the border style is ");
	switch (pstr->border_style)
	{
		case SOLID: printf("solid.\n"); break;
		case DOTTED: printf("dotted.\n"); break;
		case DASHED: printf("dashed.\n"); break;
		default: printf("unknow type.\n");
	}
}/*}}}*/

void show_setting1(unsigned short bit)
{/*{{{*/
	printf("box is %s.\n", (bit & 0x1) == 0x1 ? "opaque" : "transparent");
	printf("The fill color is %s.\n", colors[(bit >> 1) & 0x7]);
	printf("border %s.\n", ((bit >> 8) & 0x1) == 0x1 ? "shown" : "not shown");
	printf("the border color is %s.\n", colors[(bit >> 9) & 0x7]);
	printf("The border style is ");
	switch ((bit >> 12) & 0x3)
	{
		case SOLID: printf("solid.\n"); break;
		case DOTTED: printf("dotted.\n"); break;
		case DASHED: printf("dashed.\n"); break;
		default: printf("unknown type.\n");
	}
	
}/*}}}*/

char *itobs(int num, char * str)
{/*{{{*/
	int i;
	const static int size = CHAR_BIT * sizeof(int);

	for (i = size - 1; i >= 0; i--, num >>= 1)
		str[i] = (0x1 & num) + '0';
	str[size] = '\0';

	return str;
}/*}}}*/

四、对齐特性

char _Alignas(double) cz;  //以double作为对齐值

第十六章

一、明示常量:#define
[1] #define 组成
  1. 由3部分组成,第一部分是#define 指令本身。第二部分是选定的缩写,也称宏,宏名称中不允许出现空格,只能使用数字下划线和字母,且首字符不能是数字,第三部分成为替换列表或替换体

    #define TWO 2
    #define FOUR TWO*TWO
    #define PX printf("x is %d.\n", x);
    #define FMT "x is %d.\n"
    ……
    int x = TWO;
    PX;
    printf(FMT, x);
    
  2. 一般来说,预处理器发现程序中的宏后,会用宏等价的替换文本进行替换,如果替换的字符串中还包含宏,则继续替换这些宏。唯一例外的是双引号中的宏。所以 printf("TWO"); 打印的是 "TWO"

[2] #define 和 const 区别
  1. 非自动数组的大小应该是整型常量表达式,这意味着表达数组大小的必须是整型常量的组合、枚举常量和 sizeof 表达式,不包括声明的 const

    #define LIMIT 20
    const int LIM = 50;
    static int data1[LIMIT];  //有效
    static int data2[LIM];   //无效
    const int LIM2 = 2 * LIMIT;  //有效
    const int LIM3 = 2 * LIM;  //无效
    
[3] 重定义常量
  1. 除非新定义与旧定义相同(完全一样),否则就会有错
[4] 在#define 中使用参数
  1. 他和函数调用很像,但是有区别

    #include <stdio.h>
    #define SQUARE(X) X*X
    #define PR(X) printf("The result is %d.\n", X)
    
    int main(int argc, const char *argv[])
    {
    	int x = 5;
    	int z;
    
    	z = SQUARE(x);  
    	PR(z);   //正常输出 25
    	PR(SQUARE(x + 2)); //替换成 x + 2*x + 2 = 5 + 10 + 2 = 17; 只是简单的替换
        //这种可以通过加括号来纠正 (X)*(X) 
    	PR(100 / SQUARE(2));  //替换成 100 / 2 * 2 = 50 * 2 = 100; 
        //这种可以在外加括号来纠正 (X*X)
        //综上最好这样定义 #define SQUARE(X) ((X)*(X)),但是下面情况怎么样都不能纠正
    	PR(SQUARE(++x)); //替换成++x * ++x = 49,也可能是42,他会自加两次,和普通函数不同,这种情况最好避免使用define
    
        return 0;
    }
    
[5] # 运算符:用宏参数创建字符串
#define PSQR(X) printf("The square of X is %d.\n", ((x)*(x)))
//他只会输出 The square of X is (参数的值),但是我想让他显示传递参数的字符串
#define PSQR(X) printf("The square of "#x" is %d.\n", ((x)*(x)))
//#x 就是转换成字符串 x 的形参名,这个过程称为字符串化 例如
int y = 5;

PSQR(y); //输出The square of y is 5
PSQR(2 + 4)  //输出 The square of 2 + 4 is 6;
[6] ##运算符:预处理器粘合剂
  1. ## 运算符可以用作类函数的替换部分,还可用于对象宏的替换部分,他可以把两个记号组合成一个记号

    #include <stdio.h>
    #define XNAME(n) x ## n  //组合两个记号 x、n,成一个记号xn
    #define PRINT_XN printf("x"#n" = %d\n", x ## n);
    int main(void)
    {
    	int XNAME(1) = 14;  //替换成int x1 = 14;
        int XNAME(2) = 20;  //替换成int x2 = 20;
        int x3 = 30;
        
        PRINT_XN(1);  //变成 printf("x1 = %d\n", x1);
        PRINT_XN(2);  //变成 printf("x2 = %d\n", x2);
        PRINT_XN(3);  //变成 printf("x3 = %d\n", x3);
        return 0;
    }
    
[8] 变参宏:... 和 __VA_ARGS__
  1. 把宏参数列表中最后的参数写成省略号(即三个点...) 来实现这一功能,这样,预定义宏 __VA_ARGS__ 可用在替换部分,表明省略号代表什么

    #define PR(...) printf(__VA_ARGS__)
    #define PR2(X, ...) printf("Message "#x" :"__VA_ARGS__)
    
    PR("Howdy"); //对于 __VA_ARGS__ 展开只有一个参数:“Howdy”
    PR("weight = %d, shipping = $%.2f\n", wt, sp); //对于 __VA_ARGS__来说有三个参数:“weight = %d, shipping = $%.2f\n”、wt、sp
    //所以最后展开代码为
    printf("Howdy");
    printf("weight = %d, shipping = $%.2f\n", wt, sp);
    
    PR2(1, "x = %g\n", 6.9282);
    
[9] 程序员常用的宏
  1. #define MAX(X,Y) ((X) ? (Y) ? (X) : (Y))
    #define ABS(X) ((X) < 0 ? -(X) : (X))
    #define ISSIGN(X) ((X) == '+' || (X) == '-' ? 1 : 0)
    //如果x是一个代数符号字符,最后一个宏值为1, 即为真
    
二、#include :文件包含
[1] include 指令的两种形式
  1. #include <stdio.h>        //查找系统目录
    #include "mystuff.h"       //查找当前工作目录,如果没有再查找系统目录
    #include "/usr/biff/p.h"   //查找/usr/biff目录
    
  2. 在大部分情况下,头文件的内容是编译器生成最终代码时所需的信息,而不是添加到最终代码中的材料,所以包含一个大型头文件不一定显著增加程序的大小

三、其他指令
[1] #undef 指令
  1. 该指令用于取消已定义的#define指令

  2. #define LIM 20
    #undef LIM  //取消定义
    
四、条件编译
[1] #ifdef、#else 和 #endif 指令
  1. 示例

    #ifdef MAVIS
    	#include "horse.h"  //如果已经用#define 定义了 MAVIS,则执行下面的指令
    	#define STABLES 5
    #else(需要就有,不需要可以省略)
    	#include "cow.h"  //如果没有用#define 定义 MAVIS,则执行下面指令
    	#define STABLES 15
    #endif(必须要有)
    

    注:如果用的是旧编译器,必须左对齐所有的指令或者至少对齐#号

[2] #ifndef 指令
  1. 他和#ifdef 指令用法类似,但是他们的逻辑相反, #ifndef 指令判断后面的标识符是否是未定义的,常用于定义志强未定义的常量,如下所示

    #ifndef SIZE
    	#define SIZE 100
    #endif
    
  2. ifndef 指令常用于防止多次包含一个文件,所以头文件应该这样设置

    #ifndef THINGS_H_
    	#define THINGS_H_
    	……
    #endif
    
[3] #if 和 #elif 指令
  1. 它类似于C语言中的 if,#if 后面跟整型常量表达式,如果表达式为非零,则表达式为真,可以在指令中使用C的关系运算符和逻辑运算符

  2. #if SYS == 1
    #include "ibm.h"
    #endif
    
  3. 可以使用 if else 的形式使用 elif

    #if SYS == 1
    #include "ibmpc.h"
    #elif SYS == 2
    #include "vax.h"
    #elif SYS == 3
    #include "mac.h"
    #endif
    
  4. 新的编译器提供另一种方法测试名称是否已定义,即用 #if defined (VAX) 代替 #ifdef VAX, 这里的 defined 是一个预处理运算符,如果它的参数是用 #defined 定义过,则返回1, 否则返回0,它也可以和 #elif 一起使用,重写上述示例

    #if defined (IBMPC)
    	#include "ibmpc.h"
    #elif defined (VAX)
    	#include "vax.h"
    #elif defined (MAC)
    	#include "mac.h"
    #endif
    
五、预定义宏
[1] 预定义宏
含义
__DATE__ (%s) 预处理的日期(字符串字面量)
__FILE__ (%s)表示当前源代码文件名的字符串字面量
__LINE__ (%d) 表示当前源代码文件中行号的整型常量
__STDC__ (%d)设置为1时,表明实现遵循C标准
__STDC_HOSTED__ (%d)本机环境设置为1;否则设置为0
__STDC_VERSION__ (%ld)支持c99标准,设置为199901L;支持c11标准,设置为201112L
__TIME__ (%s)翻译代码的时间
__func__ (%s)函数名
六、#line 和 #error
[1] #line
  1. line 指令重置 __LINE____FILE__ 宏报告的行号和文件名, 可以这样使用#line

    #line 1000  //把当前行好重置为1000
    #line 10 "cool.c"  //把行号重置为10, 把文件名重置为 cool.c
    
[2] #error
  1. error 指令让预处理器发出一条错误消息

七、#pragma
八、泛型选择
[1] _Generic(x, int: 0, float: 1, double: 2, default: 3)
  1. 泛型选择表达式,可根据表达式的类型选择一个值,他不是预处理器指令,但是它常用作 #define 宏定义的一部分

  2. 示例(和 switch 类似)

    _Generic(x, int: 0, float: 1, double: 2, default: 3)
    //第一个项类型匹配哪个标签,整个表达式的值是该标签后面的值,如果x 匹配 int, 那么它的值就是0,如果匹配float 那么值就是1, 如果没有类型匹配,表达式的值就是 default 标签后面的值
    
  3. 泛型选择语句和宏定义组合的例子

    #define MYTYPE(X) _Generic((X),\
    	int: "int",\
    	float: "float",\
    	double: "double",\
    	default: "other",\
    ) //宏必须定义为一条逻辑行,但是可以用\把一条逻辑行分隔成多条物理行
    
    int main(void)
    {
        int d = 5;
        
        printf("%s\n", MYTYPE(d));  //d 是 int 类型    int 
        printf("%s\n", MYTYPE(2.0*d)); //2.0 * d 是 double 类型  double 
        printf("%s\n", MYTYPE(3L));  //3L是 long 类型    other
        printf("%s\n", MYTYPE(&d));  //&d 是 int * 类型 double
    }
    
九、内联函数
[1] 内联函数
  1. 把函数变成内联函数建议尽可能快地调用该函数,其具体效果由实现定义,所以编译器可能会用内联代码替换函数调用,并执行一些其他的优化,但是也可能不起作用

  2. 示例

    #include <stdio.h>
    lnline static void eatline(void)  // 内联函数模型
    {
    	while (getchar() != '\n')
    		continue;
    }
    
    int main(void)
    {
    	……
    	eatline();  //函数调用
    	……
    }
    
  3. 内联函数应该比较短小,把较长的函数变成内联函数并未节约多少时间,因为执行函数体的时间比调用函数的时间长的多

  4. 编译器优化内联函数必须直到该函数定义的内容,所以内联函数定义与函数调用必须在同一个文件中,所以一般情况下内联函数都具有内部链接,如果多个文件都要用到该函数时,可以把该内联函数放入头文件中,然后每个文件中添加头文件即可

十、_Noreturn 函数
  1. 他表示函数调用完后不反悔主调函数,和 exit() 类似
十一、C库
十二、数学库

[1] 三角问题
  1. //手动输入坐标,然后打印长度和角度
    #include <stdio.h>
    #include <math.h>
    
    #define RED_ANG (180/(4 * atan(1)))  //把弧度转换为读,4*atan(1) 等于pi
    
    struct polar {
    	double length;
    	double angle;
    };
    
    struct data {
    	double x;
    	double y;
    };
    struct polar data_polar(struct data);
    
    int main(int argc, const char *argv[])
    {
    	struct polar result;
    	struct data input;
    
    	puts("Enter x and y coordinates; ente q to quit");
    	while (scanf("%lf %lf", &input.x, &input.y) == 2)
    	{
    		result = data_polar(input);
    		printf("magnitude = %0.2f, angle = %0.2f.\n", 
    				result.length, result.angle);
    	}
    	puts("Bye!");
    
        return 0;
    }
    
    struct polar data_polar(struct data structure)
    {
    	struct polar export;
    
    	export.length = sqrt(structure.x * structure.x + structure.y * structure.y);
    	if (export.length == 0)
    		export.angle = 0.0;
    	else
    		export.angle = RED_ANG * atan2(structure.y, structure.x);
    
    	return export;
    }
    
[2] 类型变体
  1. #define RAD_ANG (180/(4 * atan1(1)))
    #define SQRT(X) _Generic((X),\
    	long double: sqrtl,\
    	default: sqrt,\
    	float: sqrtf) (X)
    
    #define SIN(X) _Generic((X),\
    	long double: sinl((x)/RAD_ANG),\
    	default: sin((X)/RAD_ANG),\
    	float: sinf((X)/RAD_ANG)\
    )
    
    int main(void)
    {
        float x = 45.0f;
        double xx = 45.0;
        long double xxx = 45.0L;
        
        long double y = SQRT(x);  //匹配 float
        long double yy = SQRT(xx);  //匹配 default
        long double yyy = SQRT(xxx);  // 匹配 long double
    }
    
十三、通用工具库
[1] atexit () 函数
  1. atexit() 函数接受一个函数指针作为参数,atexit 注册函数列表中的函数,当调用 exit() 时会执行这些函数,这个列表至少可以放32个函数,最后调用 exit() 函数时,exit() 会执行这些函数(注意:执行顺序与列表中的函数顺序相反,即最后添加的函数最先执行)
  2. atexit 注册的函数应该不带任何参数且返回类型为 void。通常这些函数会执行一些清理任务
  3. 即使没有显式调用 exit(),main() 函数结束时会隐式调用 exit(),他还是会调用一些列表中的函数
[2] exit() 函数
  1. exit 执行完 atexit 指定的函数后,会完成一些清理工作:刷新所有输出流、关闭所有打开的流和关闭由标准I/O函数 temfile() 创建的临时文件。然后 exit 把控制权返回主机环境,如果可能的话,向主机环境报告终止状态
  2. 一般unix用0表示成功终止,用非零值表示终止失败,但是为了可移植性,定义一个名为 EXIT_FAILURE 的宏表示终止失败,EXIT_SUCCESS 表示成功终止
  3. 在非递归的main 中使用 exit 函数等价于使用关键字 return 。但是在 main 函数以外的地方使用 exit 函数也会终止程序
[3] qsort函数

需要头文件 stdlib.h

  1. 原型如下

    void qsort(void *array, size_t number, size_t size, int (*compare) (const void *, const void *));

    • 第一个参数是指针,指向待排序数组的首元素,ANSI C允许把指向任何数据类型的指针强制转换成指向 void 的指针,所以,qsort 的第一个实际参数可以引用任何类型的数组。
    • 第二的参数是待排序项的数量。函数原型把该值转换成 size_t 类型
    • 第三个参数指明待排序数组中每个元素的大小。例如,如果排序 double 类型的数组,那么这个参数就是 sizeof(double)
    • 最后一个参数是一个指向函数的指针,这个指针指向的函数用于确定排序的顺序,它应该接受两个参数,分别是指向待比较两项的指针,如果第一项大于第二项,比较函数返回正数,如果相同,返回0,如果小于第二项,返回负数(此时从小到大排列),如果第一项大于第二项返回负数,反之返回正数(此时就是从大到小排列)
  2. 示例(程序清单16.17)

    #include <stdio.h>
    #include <stdlib.h>
    
    #define LEN 40
    
    void fillarray(double [], int);  //创建数组
    void showarray(double *, int); //展示数组
    int mycompare(const void *, const void *);  //比较函数
    
    int main(int argc, const char *argv[])
    {
    	double arr[LEN];
    
    	fillarray(arr, LEN);
    	puts("Random list");
    	showarray(arr, LEN);
    	qsort(arr, LEN, sizeof(double), mycompare);
    	puts("\nSorted list:");
    	showarray(arr, LEN);
    
        return 0;
    }
    
    void fillarray(double ar[], int n)
    {/*{{{*/
    	int i;
    
    	for (i = 0; i < n; i++)
    		ar[i] = (double) rand() / ((double) rand() + 0.1);
    }/*}}}*/
    
    void showarray(double * ptd, int n)
    {/*{{{*/
    	int index;
    
    	for (index = 0; index < n; index++)
    	{
    		if (index % 6 == 0 && index != 0)
    			putchar('\n');
    		printf("%9.4f ", ptd[index]);
    	}
    	if (index - 1 % 6 != 0)
    		putchar('\n');
    }/*}}}*/
    
    int mycompare(const void * p1, const void * p2)
    {/*{{{*/
    	const double * ptd1 = (const double *) p1; //注意这里的指针类型转换
    	const double * ptd2 = (const double *) p2;
    
    	if (*ptd1 < *ptd2)
    		return -1;
    	else if (*ptd1 == *ptd2)
    		return 0;
    	else
    		return 1;
    }/*}}}*/
    
  3. 注意C 和C++ 中的 void *

    • C和C++ 都可以把任何类型的指针赋给void类型的指针,但是C++要求把 void * 指针赋给任何类型的指针时必须进行强制类型转换,而C没有这样要求,但是为了可移植性,建议使用强制转换
  4. 排序字符串,假如有一个结构体,里面包含名和姓,先比较名,名相同再比较姓

    struct name {
        char first[40];
        char first[40];
    };
    struct name staff[100];
    
    qsort(staff, 100, sizeof(struct names), comp);
    
    int comp(const void * p1, const void * p2)
    {
        const struct name *pstr1 = (const struct name *) p1;
        const struct name *pstr2 = (const struct name *) p2;
        int result;
        result = strcmp(pstr1->last, ps2->last);  //比较姓
        if (result != 0)
            return result;
        else
            return strcmp(pstr1->first, pstr2->first);
    }
    
十四、断言库(assert.h)
[1] assert( ) 的用法
  1. 它接受要给整形表达式作为参数,如果表达式求值为假(非零),assert() 宏就在标准错误流(stderr)中写入一条错误信息,并调用 abort() 函数终止程序(abort() 函数原型在 stdlib.h 头文件中)。assert(x >= 0) 如果表达式为假就终止

  2. 如果 assert 终止了程序,他首先会显示失败的测试,包含测试的文件名和行号

  3. assert 可以用 if 语句来代替

    if (z < 0)
    {
    	puts("z less than 0");
    	abort();
    }
    
  4. 使用 assert 而不使用 if 来代替的原因:

    • assert 他能自动标识文件和出问题的行号
    • assert 可以无序更改代码就能开启和关闭,如果已经排除了 bug ,此时就不需要 assert 语句来测试了,在包含 assert.h 头文件前面加上 #define NDEBUG,重新编译程序,这样编译器就会禁用文件中所有的 assert 语句,如果又出现问题,可以注释#define 这条语句,然后重新编译,这样就重新启用了 assert 语句。
[2] _Static_assert
  1. _Static_assert 和 assert 不同,assert 表达式实在运行时进行检查,而 _Static_assert 是在编译的时候检查表达式的
  2. 他接受两个参数,第一个参数时整型常量表达式, 第二个参数是第一个字符串。如果第一个表达式为0(或_False),编译器就会显示字符串,而且不编译程序(即编译失败),而且他要求第一个参数时整型常量表达式,不能使用变量,这保证了他在编译期求值
  3. _Static_assert(-1 >= 0) 表达式为假,编译不通过,
十五、string.h 库中的 memcpy()和 memmove()
  1. 原型

    void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
    void *memmove(void * s1, const void * s2, size_t n);
    
  2. 这两个函数都是从 s2 指向的位置拷贝 n 字节到 s1 指向的位置, 而且都是返回 s1 的值

  3. 不同:memcpy 的参数带着关键字 restrict ,即 memcpy 假设两个内存区域之间没有重叠,而 memmove 没有这种假设

  4.  double src[SIZE];
     double dest[SIZE];
     
     memcpy(dest, src, SIZE * sizeof(double))
    
  5. memcpy 函数不知道也不关心数据类型,他只是负责从一个位置把一些字节拷贝到另一个位置,而且拷贝过程中不会进行数据转换

十六、可变参数 stdarg.h
posted @ 2021-07-04 22:17  今天的风,甚是喧嚣  阅读(223)  评论(0)    收藏  举报