C Primer Plus 习题

C Primer Plus 习题

初识C语言

​ C语言诞生于贝尔实验室,目前常用C语言标准主要是早期的美国国家标准协会制定的ANSI C标准,以及国际标准化组织(ISO组织)制定的C99和C11标准。

​ 通过编译器将C语言源代码转化为机器指令的过程称为编译,流程可以表示如下:
​ 源代码->目标代码(又称中间代码,在这个阶段编译器可以对指令进行优化)->链接(链接生成的目标文件和外部引用的(不必编译的)库文件)->生成可执行程序。

​ 典型的C语言的源代码文件一般由预处理器指令、main()函数以及其他各种C语言的语句组成。
预处理器指令以#开头,如#include、#define、#ifndef、#endif。

数据和C

基本数据类型:整型、浮点型、字符型

float类型数据由6位有效数字,double类型数据有10位有效数字

#include <stdio.h>
printf("%#x\n",3);//x小写
printf("%#X\n",3);//x大写
printf("%e\n",30.1415926535);
printf("%#o\n",9);

/*运行结果
0x3
0X3
3.014159e+01
011
*/

转义符

  • \n 表示换行符
  • \\ 表示\
  • \" 表示"
  • \t 表示水平制表符

不定长字符串

#include <stdio.h>
#include <stdarg.h>
 
double average(int num,...)
{
 
    va_list valist;
    double sum = 0.0;
    int i;
 
    /* 为 num 个参数初始化 valist */
    va_start(valist, num);
 
    /* 访问所有赋给 valist 的参数 */
    for (i = 0; i < num; i++)
    {
       sum += va_arg(valist, int);
    }
    /* 清理为 valist 保留的内存 */
    va_end(valist);
 
    return sum/num;
}
 
int main()
{
   printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

运算符、表达式和语句

  • 基本运算符

​ 能对一个数据或变量进行操作的运算符称为一元运算符,如递增/减运算符;

​ 需要对两个数值或变量进行操作的运算符称为二元运算符,如加法运算符。

复习题:

y = x = (2 + 3) / 4;			//x = 1, y = 1
y = 3 + 2 * (x = 7 / 2);	//x = 3, y = 9     (x = 7 / 2)会返回3
n = 10;

printf("%d\n",++n);	//先递增再参与打印,打印结果为11,n实际值为11
printf("%d\n",n++);	//先参与打印再递增,打印结果为11,n实际值为12    n++可以理解为先返回当前值再自增值
printf("%d\n",n--);	//先参与打印再递减,打印结果为12,n实际值为11
printf("%d\n",n);		//打印结果为11

补充:scanf()函数又返回值,返回读取(指定)数据的数量

循环

​ 练习题目:编写1个程序,读取1行输入,然后把输入的内容倒序打印出来。可以把输入存储在char 类型的数组中,假设每行字符不超过255个。

​ 编程分析:程序的关键在于读取用户输入的函数,可以使用换行符来判断用户输入完毕。

void fun(void) {
    char data[256];
    int i = 0;
    do {
        scanf("%c",&data[i]);
    } while(data[i] != '\n' && ++i); //++i很妙

    for(i--;i>=0;i--) {
        printf("%c",data[i]);				 //i--很细
    }
    printf("\n");
}

分支和跳转

​ ABC邮购杂货店出售的洋蓟售价为2.05美元/磅(1磅=0.45359237千克),甜菜售价为1.15美元/磅,胡萝卜售价为1.09美元/磅。在添加运费之前,100美元订单有5%的打折优惠。少于或者等于5磅的订单收取6.5美元的运费和包装费,5~20磅的订单收取14美元的运费和包装费,超过20磅的订单在14美元的基础上每续重1磅增加0.5美元。编写一个程序,在循环中用 switch 语句实现用户输入不同字母时有不同响应,即输入a 的响应是输入洋蓟的磅数,b是甜菜的磅数,c是胡萝卜的磅数,q是退出订购。程序要记录累计的重量。即,如果用户输入4磅的甜菜,然后输入5磅的甜菜,程序应该报告9磅的甜菜。然后,该程序要计算货物总价、折扣(如果有的话)、运费和包装费。随后,程序应显示所有的购买信息,包括物品售价、订购的重量(单位是磅)、订购的蔬菜费用、订单的总费用、折扣(如果有的话)、运费和包装费,以及费用总额。

#define PRICE_Y 2.05
#define PRICE_T 1.15
#define PRICE_H 1.09
#define DISCOUNT 0.05

void show_menu(void) {
    printf("******************************************************\n");
    printf("Enter the char corresponding to the desired vegetable.\n");
    printf(" a) Y          b) T\n");
    printf(" c) H          q) Quit or Checkout\n");
    printf("******************************************************\n");
    printf("Please input the vegetable you want to buy(a,b,c or q for quit):");
}

int get_weight(void) {
    float weight = 0.0;
    printf("Please input how many pound you buy:");
    scanf("%f",&weight);
    printf("OK, add %g pound to cart.\n",weight); //%g将无用的0过滤掉
    getchar(); //吸收\n...  很好的技巧
    return weight;
}

void fun(void) {
    
    
    float w_Y = 0;
    float w_T = 0;
    float w_H = 0;
    char selected = 0;
    
    do {
        show_menu();
        scanf("%c",&selected); //也可以这么写:selected = getchar();while(getchar() != '\n');
        switch (selected) {
            case 'a':
                w_Y += get_weight();
                break;
            case 'b':
                w_T += get_weight();
                break;
            case 'c':
                w_H += get_weight();
                break;
            case 'q':
                break;
            default:
                printf("Error input, retry\n");
                break;
        }
    } while (selected != 'q');
    
    float money = w_Y * PRICE_Y + w_T * PRICE_T + w_H * PRICE_H;
    float weight = w_Y + w_T + w_H;
    float rebate = 0;
    float freight = 0;
    if (money >= 100) {
        rebate = money * DISCOUNT;
    }
    
    if (weight <= 5) {
        freight = 6.5;
    } else if (weight <= 20) {
        freight = 14;
    } else {
        freight = 14 + (weight - 20) * 0.5;
    }
    
    float total = money + freight - rebate;
    
    printf("The price of vegetable: Y %g$/pound, T %g$/pound, H %g$/pound.\n",PRICE_Y,PRICE_T,PRICE_H);
    printf("You order %g pound Y,  %g pound T,  %g pound H.\n",w_Y,w_T,w_H);
    printf("You total order %g pounds, discount %g$, amount %g$, freight %g$, totla %g$.\n",weight,rebate,money,freight,total);
}

字符输入/输出和输入验证

selected = getchar();
while(getchar() != '\n'); //使用getchar()吸收数据缓冲区中的无效字符,防止对后续的影响

while(scanf("%g",&f) != 1) {
  while((c = getchar()) != '\n')
    putchar(c);						//可以把无效(错误)输入的内容打印出来
} 

函数

  • 实参和形参的区别:

    形参是定义在被调函数中的局部变量,只在该函数的函数体内有效。

    实参是在函数调用过程中由主调函数赋予函数的值,在函数调用中,实参的值被赋予形参,初始化了形参,从而将主调函数内的数据传递入被调函数内。

  • 递归是某些特定问题的简单有效的解决方案,但递归调用本身对资源的消耗过大且代码不易于理解,因此应当谨慎使用。

进制转换:将10进制数转换成n进制(2~8)

unsigned long fun(unsigned long n, unsigned short t) {
    if (t < 2 || t > 10) {
        printf("The function only convert 2 ~ 10\n");
        return -1;
    }
    
    unsigned long result = 0;
    int place = 1;
    while (n > 0) {
        int r = n % t;
        result += r * place;
        place *= 10;
        n /= t;
    }
    return result;
}

int main(void) {
    unsigned long number;
    unsigned short target;
    printf("Enter the integer and N for notation(q to quit):");
    while (scanf("%lu %hu",&number,&target) == 2) {
        if (target < 2 || target > 10) {
            printf("Please input N between 2 ~ 10!\n");
            printf("Enter the integer and N for notation(q to quit):");
            continue;
        }
        unsigned long result = fun(number, target);
        if (result != -1) {
            printf("Convert %lu to %hu notation is:%lu\n",number,target,result);
            printf("Enter the integer and N for notation(q to quit):");
        }
    }
    return 0;
}

循环实现斐波那契数列(非递归)

void fun(int n) {
    unsigned long f1,f2,temp;
    
    f1 = 1;
    f2 = 1;
    
    for (int i = 0; i < n; i++) {
        printf("%lu ",f1);
        temp = f1 + f2;
        f1 = f2;
        f2 = temp;
    }
    putchar('\n');
}

int main(void) {
    
    int n;
    printf("Enter the number of Fibonacci (q to quit):");
    while (scanf("%d",&n) == 1) {
            fun(n);
            printf("Enter the number of Fibonacci (q to quit):");        
    }
    
    return 0;
}

数组和指针

​ 数组名是一个常量,因此不能够被再次赋值,也不能做递增或者递减运算。而字符指针通常是常量。可以再次赋值(指向其他字符串),也可以实现递增和递减操作。

​ 指针是一种以符号形式使用数据存储地址的方法。指针数据的本质就是一个数据地址,系统可以根据该地址快速检索到该地址所存储的数据。声明一个指针需要指明该指针存储的地址所指向数据的数据类型,并使用*号来标识指针变量,例如,int *p;声明了一个指针,存储的地址指向的数据为整型。在使用过程中*表示间接操作,即由操作数指针所存储的地址寻值;&表示取地址操作,即获取该操作数的存储地址。指针的加减操作本质上是将指向的地址向前或者向后移动对应类型的字节宽度。对于指针变量的基本操作包括赋值(将一个指针赋值给另一指针后,两个指针同时指向同一个地址)、解引用、取地址、递增、递减、与整数加减、指针变量的比较和减法操作。

​ 使用指针表示二维数组arr[10][10]元素arr[0][0]的三种方法:&arr[0][0]、arr[0]、(int *)arr.特别要注意的是(int *)arr这种表示方法,arr是二维数组的地址(常量,不可进行赋值操作),虽然和arr[0][0]的地址相同,但是类型不同,因此需要通过强制类型转换(int *),将其转换成指向整型数据的地址。

int *psa[20];		//表示psa是一个数组,内含20个整型指针(本质是数组,元素是整型指针int*)
int (*psa)[20];	//表示psa是一个指针,指向一个含有20个整型元素的数组(本质是指针,元素是整型int)

int pots[100] = {[5] = 1, [10] = 1, [99] = -1};	//C99/C11标准下,可以这样初始化数组(下标为5的元素值为1...)

​ 复合字面量也称数值常量,如**{8,2,1,4}**,把复合字面量作为匿名数组来使用会很方便快捷:

void show(const double arr[],int n);	//声明

show((int[4]){8,2,1,4},4);	//调用

​ 复制一维数组的三种方式:

void copy_arr(double t[], double s[], int n) {   //目标数组,源数组,要复制的元素的个数
    for (int i = 0; i < n; i++) {
        t[i] = s[i];
    }
}

void copy_ptr(double *t, double *s, int n) {		 //指向目标数组的指针,指向源数组的指针,要复制的元素的个数
    for (int i = 0; i < n; i++) {
        *(t+i) = *(s+i);
    }
}

void copy_ptrs(double *t, double *s_first, double *s_last) { 	//指向目标数组的指针,源数组的起始位置和结束位置
    long len = s_last - s_first;    //len的值为:从s_first到s_last之间包含的double类型元素的个数 重点!!!

    for (int i = 0; i < len; i++) {
        *(t + i) = *(s_first + i);
    }
}

int main(void) {
    double source[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
    double target1[5];
    double target2[5];
    double target3[5];
    
    copy_arr(target1, source, 5);
    copy_arr(target2, source, 5);
    copy_ptrs(target3, source, source + 5);
    
    return 0;
}

​ 复制二维数组:

#define ROWS 3
#define COLS 2

void copy_arr(double t[], double s[], int n) {
    for (int i = 0; i < n; i++) {
        t[i] = s[i];
    }
//    for (int i = 0; i < COLS; i++) {
//        printf("%g\t",t[i]);
//    }
//    putchar('\n');
}

void copy_ptr(double *t, double *s, int n) {
    for (int i = 0; i < n; i++) {
        *(t+i) = *(s+i);
    }
//    for (int i = 0; i < COLS; i++) {
//        printf("%g\t",t[i]);
//    }
//    putchar('\n');
}

void copy_ptrs(double *t, double *s_first, double *s_last) {
    long len = s_last - s_first;    //len的值为:从s_first到s_last之间包含的double类型元素的个数 重点!!!

    for (int i = 0; i < len; i++) {
        *(t + i) = *(s_first + i);
    }
}

void copy_2d_arr(double t[][COLS], double s[][COLS], int n) {
    for (int i = 0; i < n; i++) {
        copy_arr(t[i], s[i], COLS);
    }
}

void copy_2d_ptr(double (*t)[COLS], double (*s)[COLS], int n) {
    for (int i = 0; i < n; i++) {
        copy_ptr(t[i], s[i], COLS);
    }
}

int main(void) {
    double target[ROWS][COLS], source[ROWS][COLS] = {
        {1.1, 1.2},
        {2.1, 2.2},
        {3.1, 3.2},
    };
    copy_2d_ptr(target, source, COLS);
    return 0;
}

​ 编写一个程序,提示用户输入3组数,每组数包含5个 double 类型的数(假设用户都正确地响应,不会输入非数值数据)。该程序完成下列任务:

  1. 把用户输入的数据存储在3x5 数组中;

  2. 计算每组(5个)数据的平均值;

  3. 计算所有数据的平均值;

  4. 找出这15个数据中的最大值;

  5. 打印结果。

​ 每个任务都要用单独的函数来完成。为了完成任务2,要编写一个计算并返回一维数组平均值的函数,利用循环调用该函数3次。对于处理其他任务的函数,应该以整个数组作为参数,完成任务3和4的函数应把结果返回主调函数。

(本次使用了动态分配内存创建数组的方法,旨在解题同时熟悉(二级)指针、malloc和free的用法及其注意事项,事实证明自己缺乏实际运用经验,途中出现了很多问题,幸得豆包帮助得以完成。倘若不以此为目的,直接静态创建一个3✖️5的数组未尝不可,可省许多麻烦事。为了不改动源数组的值,可以在调用函数传递数组时使用const关键字。)

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

#define ROWS 3
#define COLS 5

double** TaskA_Store(void) {
    double **input = (double **)malloc(ROWS * sizeof(double *));
    if (input == NULL) return NULL;
    for (int i = 0; i < ROWS; i++) {
        input[i] = (double *)malloc(COLS * sizeof(double));
        if(input[i] == NULL) return NULL;
    }
    
    printf("Please enter the array number.\n");
    for (int i = 0; i < ROWS; i++) {
        printf("Enter five double number seprate by enter:\n");
        for (int j = 0; j < COLS; j++) {
            scanf("%lf",&input[i][j]);
        }
    }
 
    return input;
}

double* TaskB_PerAver(double **array) {
    double *aver = (double *)malloc(ROWS * sizeof(double));
    for (int i = 0; i < ROWS; i++) {
            aver[i] = 0;
        }
    
    for (int row = 0; row < ROWS; row++) {
        for (int col = 0; col < COLS; col++) {
            aver[row] += array[row][col];
        }
        aver[row] /= COLS;
    }
    
    return aver;
}

double TaskC_AllAver(double **array) {
    double result = 0.0;
    
    double *aver = TaskB_PerAver(array);
    for (int i = 0; i < ROWS; i++) {
            result += aver[i];
        }
    
    free(aver);
    
    return result / ROWS;
}

double TaskD_FindMax(double **array) {
    double max = array[0][0];
    
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            if (array[i][j] > max) {
                max = array[i][j];
            }
        }
    }
    
    return max;
}

void TaskE_Print(double **array) {
    for (int i = 0; i < ROWS; i++) {
        for (int j = 0; j < COLS; j++) {
            printf("%g\t",array[i][j]);
        }
        putchar('\n');
    }
}



int main(void) {
    double **Matrix = TaskA_Store();
    if (Matrix == NULL) {
        printf("出错了,请重新运行程序!\n");
        return 0;
    }
    printf("OK! The data you input is:\n");
    TaskE_Print(Matrix);
    
    double *per_aver = TaskB_PerAver(Matrix);
    printf("The average value of each group of data:\n");
    for (int i = 0; i < ROWS; i++) {
        printf("%g\t",per_aver[i]);
    }
    putchar('\n');
    
    printf("The average value of all data:\n%g\n",TaskC_AllAver(Matrix));
    
    double max = TaskD_FindMax(Matrix);
    printf("The maximum value in all data is:\n%g\n",max);
    
    // 释放内存,先释放Matrix中每行指向的内存
    for (int i = 0; i < ROWS; i++) {
        free(Matrix[i]);
    }
    // 再释放Matrix指针数组本身指向的内存
    free(Matrix);

    free(per_aver);  // 释放TaskB_PerAver返回的结果指针指向的内存
    
    return 0;
}

常量指针和指向常量的指针

常量指针 *char const p;

p是一个常量指针,它指向的地址不能改变,但是可以通过这个指针修改它所指向的内容

char c = 'a';
char * const str = &c;
*str = 'b';  // 合法,可以修改指针所指向的内容
// str = &c2;  // 非法,不能修改指针本身的值,会导致编译错误

指向常量的指针 *const char p;

p是一个指针,它指向的内容是常量(只读),可以操纵指针p进行遍历,不能通过指针p进行修改

const char *str = "Hello";
printf("%c", *str++);  // 合法,输出 'H'
// *str = 'h';  // 非法,会导致编译错误,因为不能修改指向的常量内容

有个(区分)记忆方法:其实*这个符号,代表了定义的是一个指针,*这个符号前面的类型关键字(char、int等)则是说明了指针指向的数据的类型。const关键字放在*前边意味着修饰的是指针指向的数据而非指针本身,而const放在*后面则意味着修饰的是指针本身而非指针指向的数据。

字符串和字符串函数

​ 字符串是以空字符(‘\0’)结尾的char类型的数组。编程中可以使用const关键字来声明和限定一个字符串常量,在函数的参数传递过程中会经常用到,在应用中如果需要修改字符串内容,可以使用字符数组形式来表示字符串。

字符串输出

函数函数声明说明
puts()int puts(const char *str);直接将字符串的首地址作为参数传递,并且会自动在打印末尾添加换行符

字符串处理

函数函数声明说明
strlensize_t strlen(const char *str);用于返回字符串长度(不含’\0’)
strcatchar *strcat(char *dest,const char *src);将src字符串复制到dest字符串末尾
strncatchar *strncat(char *dest,const char *src,size_t count);可以指定复制的字符数到dest字符串末尾
strcmpint strcmp(const char *a,const char *b);用于进行字符内容的比较,相同时返回0
strnmpint strcmp(const char *a,const char *b,size_t count);可以指定比较字符串的部分数据
strcpychar *strcpy(char *dest,const char*src);将src字符串复制(覆盖)到dest字符串
strncpychar *strcpy(char *dest,const char*src,size_t count);可以指定复制的字符数(覆盖)到dest字符串
sprintfint sprintf(char *buff,const char *format,…);将多个字符串组合写入buff中
strchrchar *strchar(const char *str, char c);返回字符串内第一个指定字符的指针

其他函数

atoi()和atof()将数字形式的字符串转换成整型数据或者浮点型数据

toupper()函数返回传入字母(a~z)对应的大写字母(A~Z)

isalpha()函数判断一个字符是否是字母(a~z,A~Z)

int main(void) {
    char str[] = "hello, WORLD";
    for (int i = 0; str[i]!= '\0'; i++) {
        str[i] = toupper(str[i]);
    }
    printf("%s\n", str);
    return 0;
}

复习题

char name[] = {'W', 'u', 'X', 'i', 'a', 'o'}; //只是字符数组而非有效的字符串
char name[] = {'W', 'u', 'X', 'i', 'a', 'o', '\0'}; //字符串
char name[] = "WuXiao";	//若使用双引号标识字符串,系统会自动在数组的末尾添加空字符

倒序逐个字母打印:

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

int main(void) {
    char name[] = "WuXiao";
    char *p = name;
    
    p = name + strlen(name);
    while (--p >= name) {
        puts(p);
    }
    
    return 0;
}

对称打印:

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

char *pr(char *str) {
    char *pc;
    
    pc = str;
    while (*pc) {  //是否指向一个空字符串
        putchar(*pc++);
    }
    do {
        putchar(*--pc); //自增优先级高于取值
    } while (pc - str); //指针pc是否指向str字符串的头
    
    return pc;
}

int main(void) {
    char *x;
    x = pr("Ho Ho Ho!");
    putchar('\n');
    
    return 0;
}

自制strlen()函数:

int my_strlen(char *input_str) {
    if (input_str == NULL )
        return 0;
  
    int string_len = 0;
    while (*input_str != '\0') {
        string_len++;
        input_str++;
    }
    return string_len;
}

查找并返回输入字符串的第一个空格字符:

char* strblank(const char *str) {
    while (*str != '\n' && *str != ' ') {
        str++;
    }
    
    if (*str == '\0') {
        return NULL;
    } else {
        return (char*)str;
    }
}

scanf()、gets()函数无法处理空白字符(空白、制表符、换行符),getchar()可以.

自制strncpy()函数:

char *mystrncpy(char* dest, char* src, int n) {
  int cnt = 0;
  while (*src != '\0' && cnt < n) {
    *(dest + cnt++) = *src++;
  }
  
  /*字符串dest不为空,且src字符串长度不足n即cnt<n时,填补空白*/
  if (cnt < n) {
    *(dest + cnt++) = '\0';
  }
  return dest;
}

查找一个字符串中的子串,基本原理:当第一个字符匹配时,开始进行计数,当连续匹配时,开始递增计数,直到匹配完整子串,否则清空匹配计数器,主串开始移动一个字符,重新开始匹配计数。匹配成功后,匹配计数器的数值就等于匹配子串中的字符个数。

#include <stdio.h>

int my_strlen(char *input_str) {
    if (input_str == NULL )
        return 0;
  
    int string_len = 0;
    while (*input_str != '\0') {
        string_len++;
        input_str++;
    }
    return string_len;
}

char* string_in(char* st, char* sub) {
    int cnt = 0;
    int sub_len = my_strlen(sub);
    
    //主串st不能是空串
    while (*st != '\n' && cnt < sub_len) {
        if (*(st + cnt) == *(sub + cnt)) {
            cnt++;
        } else {
            cnt = 0;
            st++;
        }
    }
    
    if (cnt == sub_len) {  //完全匹配
        return st;
    } else {
        return NULL;
    }
}

翻转字符串并取代原字符串:

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

int my_strlen(char *input_str) {
    if (input_str == NULL )
        return 0;
    int string_len = 0;
    while (*input_str != '\0') {
        string_len++;
        input_str++;
    }
    return string_len;
}

char *invert_str(char *st) {
    int len = my_strlen(st);
    char *invert = (char *)malloc((len + 1) * sizeof(char));
    if (invert == NULL) {  // 检查内存分配是否成功
        return NULL;
    }
    
    for (int i = 0; i < len; i++) {
        invert[i] = st[len-1-i];
    }
    return invert;  //不考虑在函数内部释放动态申请的内存且不更新st可以直接返回invet
    
    /*在函数内部释放掉动态申请的内存,并更新st,要多增加复制的操作*/
//    for (int i = 0; i < len; i++) {
//        st[i] = invert[i];
//    }
//    free(invert);
//
//    return st;
}

删除字符串中的空格并调整字符串

char *trim_str(char *st) {
    char *cur = st;  //用来遍历
    char *head = st; //用来修改
    
    while (*cur != '\0') {
        if (*cur != ' ') {
            *head++ = *cur;
        }
        cur++;
    }
    *head = '\0';
    return st;  //遍历并修改完后返回原字符首地址
}

比scanf、gets、getchar和fgets函数:

  • scanf:用于从标准输入读取格式化的数据。它可以读取各种数据类型,如整数、浮点数、字符和字符串等。根据不同的格式说明符(如%d%f%c%s)来确定读取的数据类型和方式。以空白字符(空格、制表符、换行符)作为字符串结束的标志。当读取字符串时,遇到第一个空白字符就停止读取,不会包含空白字符在读取的字符串中。成功时返回成功读取的项数,如果在读取过程中遇到错误或者文件结束(EOF),返回EOF(通常是 - 1)或者小于预期读取的项数。适用于读取多种格式化的数据,在需要按照特定格式(如读取整数、浮点数和格式化后的字符串)输入数据时非常方便。
  • gets:专门用于从标准输入读取一行字符串,遇到换行符'\n'结束读取,并将换行符丢弃,不会存储到字符数组中。读取字符串时会忽略空白字符,将它们作为字符串的一部分进行读取,直到遇到换行符。是不安全的函数,因为它不会检查输入的字符串长度是否超过了数组的大小,很容易导致缓冲区溢出。成功时返回读取的字符串的首地址,出错或者遇到文件结束(EOF)时返回NULL。在只需要简单地读取一行不包含换行符的字符串,并且确定输入长度不会超过数组大小时可以使用,但由于安全性问题,现在很少推荐使用。
  • getchar:每次从标准输入读取一个字符,包括空白字符(空格、制表符、换行符)等。可以读取包括空白字符在内的任何单个字符。它不是专门用于读取字符串,但可以通过多次调用构建字符串来包含空白字符。成功时返回读取到的字符的 ASCII 码值,如果遇到文件结束(EOF),返回EOF(通常是 - 1)。常用于逐个字符地处理输入,如实现简单的文本编辑器的字符输入部分或者在读取字符序列时需要对每个字符进行单独判断的场景。
  • fgets:从指定的流(通常是stdin代表标准输入)读取一行字符串(或指定长度的字符串),会将换行符'\n'也读取进来(如果输入的字符串长度小于指定长度),并存储到字符数组中。相对安全,因为它要求指定要读取的最大字符数,这样可以有效避免缓冲区溢出。成功时返回读取的字符串的首地址(也就是字符数组的首地址),如果遇到文件结束(EOF)或者出错,返回NULL。用于安全地读取一行字符串,特别是在不确定输入长度或者需要对输入长度进行严格控制的情况下,是读取字符串的较好选择。

​ 编写一个程序,读入5个字符串或者在读到EOF 时停止。该程序为用户提供一个有5个选项的菜单,5个选项分别是打印原始字符串列表,以ASCI码中的顺序打印字符串,按长度递增顺序打印字符串,按字符串中第1个单词的长度打印字符串,退出。菜单可以循环显示,除非用户选择“退出”选项。当然,该程序要能真正实现菜单中各选项的功能。

​ 编程分析:

​ 题目要求对10个字符串进行多功能排序,在程序设计中需要首先解决字符串的数据存储形式,本题可以使用字符串数组的形式(即二维字符数组)来存储数据。程序还要求按照多种模式对字符串进行排序。菜单要求的排序形式有原序、按 ASCII 码中的顺序排序、按长度排序、按单词排序等。为了实现多关键字排序功能,程序需要提前提取每一个字符串的关键字特征值,如字符串长度、单词长度等,再将关键字转换为统一类型的数据并排序处理。下面的代码使用二维数组而没有使用字符串指针排序的主要原因是为了综合处理多种排序形式。

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

#define SIZE 80
#define NUMBER 5

void show_menu(void) {
    printf("=============================================================\n");
    printf("1) print original strings.  2)print string by ascii order\n");
    printf("3) print string by length.  4)print string by word length\n");
    printf("5) quit.\n");
    printf("=============================================================\n");
}

void input_string(int number, char st[][SIZE]) {
    printf("Please input 5 strings serperate by enter.\n");
    for (int i = 0; i < number; i++) {
        fgets(st[i], SIZE, stdin);
    }
}

void print_original(int number, char st[][SIZE]) {
    printf("print 5 strings in original mode.\n");
    for (int i = 0; i < number; i++) {
        printf("%d. %s",i,st[i]);
    }
}

void sort_order(int number, int order[][2]) {
    int temp[2];
    for (int i = 0; i < number - 1; i++) {  //冒泡排序
        for (int j = 0; j < number - 1 - i; j++) {
            if (order[j][1] > order[j+1][1]) {
                temp[0] = order[j][0];
                temp[1] = order[j][1];
                order[j][0] = order[j+1][0];
                order[j][1] = order[j+1][1];
                order[j+1][0] = temp[0];
                order[j+1][1] = temp[1];
            }
        }
    }
}

void print_ascii(int number, char st[][SIZE]) {
    printf("print 5 strings in ascii mode.\n");
    int order[number][2];
    for (int i = 0; i < number; i++) {
        order[i][0] = i;        //记录在st中的位置,方便后续打印
        order[i][1] = st[i][0]; //记录首字母作为排序特征值
    }
    sort_order(NUMBER,order);
    for (int i = 0; i < number; i++) {
        printf("ASCII No.%d. %s",i,st[order[i][0]]);
    }
}

void print_length(int number, char st[][SIZE]) {
    printf("print 5 strings in length mode.\n");
    int order[number][2];
    for (int i = 0; i < number; i++) {
        order[i][0] = i;
        order[i][1] = (int)strlen(st[i]);
    }
    sort_order(NUMBER,order);
    for (int i = 0; i < number; i++) {
        printf("LENGTH No.%d. %s",i,st[order[i][0]]);
    }
}

int get_firstword_length(char *input) {
    char *in = input;
    int length = 0;

    while (isalpha(*in++)) {
        length++;
    }
    return length;
}

void print_words(int number, char st[][SIZE]) {
    printf("print 5 strings in words mode.\n");
    int order[number][2];
    for (int i = 0; i < number; i++) {
        order[i][0] = i;
        order[i][1] = get_firstword_length(st[i]);
    }
    sort_order(NUMBER,order);
    for (int i = 0; i < number; i++) {
        printf("WORDS No.%d. %s",i,st[order[i][0]]);
    }
}

int main(void) {
    char test[NUMBER][SIZE];
    int selected;
    input_string(NUMBER, test);
    show_menu();
    scanf("%d",&selected);
    while (selected !=5) {
        switch (selected) {
            case 1:
                print_original(NUMBER,test);
                break;
            case 2:
                print_ascii(NUMBER,test);
                break;
            case 3:
                print_length(NUMBER,test);
                break;
            case 4:
                print_words(NUMBER,test);
                break;
            default:
                printf("Error select, retry!\n");
        }
        show_menu();
        scanf("%d",&selected);
    }
    printf("All done, bye.\n");
    
    return 0;
}

​ 统计输入字符串的单词数、大写字母数、小写字母数、标点符号数和数字字符数。(使用ctype.h头文件中的函数)

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

#define SIZE 256

void input_string(char st[SIZE]) {
    printf("Please input string:\n");
    fgets(st, SIZE, stdin);
}

void print_string(char *st) {
    printf("echo: ");
    while (*st != '\0') {
        putchar(*st++);
    }
}

int check_words(char *st) {
    int cnt = 0;
    int start = 0;
    
    while (*st != '\0') {
        if (isalpha(*st)) {
            st++;
            start = 1;
        } else if (start) {
            cnt++;
            start = 0;
            st++;
        } else {
            st++;
        }
    }
    if (start) {
        cnt++;
    }
    
    return cnt;
}

int check_upper(char *st) {
    int cnt = 0;
    
    while (*st != '\0') {
        if (isupper(*st++)) {
            cnt++;
        }
    }
    
    return cnt;
}

int check_lower(char *st) {
    int cnt = 0;
    
    while (*st != '\0') {
        if (islower(*st++)) {
            cnt++;
        }
    }
    
    return cnt;
}

int check_punct(char* st) {
    int cnt = 0;
    
    while (*st != '\0') {
        if (ispunct(*st++)) {
            cnt++;
        }
    }
    
    return cnt;
}

int check_digit(char* st) {
    int cnt = 0;
    
    while (*st != '\0') {
        if (isdigit(*st++)) {
            cnt++;
        }
    }
    
    return cnt;
}

int main(void) {
    char input[SIZE];
    
    input_string(input);
    print_string(input);
    
    printf("Input words %d.\n",check_words(input));
    printf("Input upper char %d.\n",check_upper(input));
    printf("Input lower char %d.\n",check_lower(input));
    printf("Input punct char %d.\n",check_punct(input));
    printf("Input digital %d.\n",check_digit(input));
    
    printf("All done, bye.\n");
    
    return 0;
}

​ 自制atoi函数(将字符串转换成整型数据),核心思路是先获取字符串长度,然后从字符串末尾从后往前把字符转换成数字

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

int myatoi(char *st) {
    int result = 0;
    int bit_mark = 1;
    int length = (int)strlen(st);
    
    for (int i = length; i > 0; i--) {
        if (isdigit(*(st + i - 1)) == 0) {
            printf("Error in character.\n");
            return 0;
        }
        result += (*(st + i - 1) - '0') * bit_mark;
        bit_mark *= 10;
    }
    return result;
}

存储类别、链接和内存管理

int *p1 = (int *)malloc(100 * sizeof(int));	
int *p2 = (int *)calloc(100 * sizeof(int));		//calloc把数组中每个元素都设置为0

​ 生成1000个介于1~10的随机数,打印每个数出现的次数,用10个不同的种子运行10轮。

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

#define SIZE 10
#define LENGTH 1000

int main(int argc, char *argv[]) {
    int data_count[SIZE+1] = {0};
    int datum;
    for (int seed = 1; seed <= 10; seed++) {
        printf("This is %d round to create data.\n",seed);
        srand(seed);        //rand函数依赖于srand函数设置的种子来生成随机数序列,不设置种子的话,rand就使用默认种子,每次生成的随机数序列都一致
        
        for (int i = 0; i < LENGTH; i++) {
            datum = rand() % 10 + 1;    //rand函数用于生成一个伪随机整数
            data_count[datum]++;
        }
        printf("Random data created, let's start it.\n");
        for (int i = 1; i <= SIZE; i++) {
            printf("The datum %d created %d times.\n",i,data_count[i]);
        }
    }
    return 0;
}

​ 通过用户的输入创建指定大小的字符串数组,并依次读取用户输入的字符串

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

int main(int argc, char* argv[]) {
    int amount;
    scanf("%d",&amount);
    char **pst = (char **)malloc(amount * sizeof(char*));
    if(pst == NULL) return 0;
    for (int i = 0; i < amount; i++) {
        char temp[100];
        scanf("%s",temp);
        int length = (int)strlen(temp);
        char *str = (char *)malloc(length * sizeof(char));
        strcpy(str, temp);
        *(pst + i) = str;
    }
    for (int i = 0; i < amount; i++) {
        printf("%s\n",*(pst + i));
        free(*(pst + i));   //释放每个字符串所占动态内存
    }
    free(pst);  //释放指针数组本身所占用内存
    /*释放一个二级指针所占内存需要进行两次释放*/
    return 0;
}

文件输入/输出

(跳过)

结构和其他数据形式

结构和结构变量

​ 结构体是C语言中进行复杂数据描述的重要手段,它可以将多个简单数据类型组合在一起,其中的每一个简单数据类型叫做结构的成员。结构作为函数的参数即可以传递结构变量,也可以传递结构的地址。作为参数传递结构指针效率更高,但是需要注意函数体内对结构数据的访问保护;以结构变量作为函数的参数能够防止实参的数据被修改,但是效率略低。

4个航班的座位预定:(此处每个航班固定只有12个座位,计划后面复习链表时使用链表动态管理座位信息)

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

struct seat {
    int id;
    int booked;
    char fname[20];
    char lname[20];
} list[4][12] = {};

void show_menu(void);
void get_empty(struct seat list[]);
void show_empty(struct seat list[]);
void show_booked(struct seat list[]);
void book_seat(struct seat list[]);
void cancel_book(struct seat list[]);

void show_menu(void) {
    puts("To choose a function, enter its letter label: ");
    puts("a) Show number of empty seats ");
    puts("b) Show list of empty seats ");
    puts("c) Show alphabetical list of seats ");
    puts("d) Assign a customer to seat assignment ");
    puts("e) Delete a seat assignment ");
    puts("f) Quit");
}

void get_empty(struct seat list[]) {
    int sum = 0;
    for (int i = 0; i < 12; i++) {
        if (list[i].booked == 0)
            sum++;
    }
    printf("There are %d seats empty\n",sum);
}

void show_empty(struct seat list[]) {
    printf("Empty list:");
    for (int i = 0; i < 12; i++) {
        if (list[i].booked == 0) {
            printf("%d ",(i+1));
        }
    }
    putchar('\n');
}

void show_booked(struct seat list[]) {
    struct seat* ptstr[12];
    for (int i = 0; i < 12; i++) {
        ptstr[i] = &list[i];
    }
    int top, seek;
    struct seat* temp;
    for (top = 0; top < 12 - 1; top++) {   //选择排序法
        for (seek = top + 1; seek < 12; seek++) {
            if (strcmp(ptstr[top]->fname,ptstr[seek]->fname) > 0) {
                temp = ptstr[top];
                ptstr[top] = ptstr[seek];
                ptstr[seek] = temp;
            }
        }
    }
    puts("Alphabetical list:");
    for (int i = 0; i < 12; i++) {
        if (ptstr[i]->booked) {
            printf("Seat No:%d bookd by %s %s\n",(i+1), ptstr[i]->fname, ptstr[i]->lname);
        }
    }
}

void book_seat(struct seat list[]) {
    int id;
    char fname[20], lname[20];
    show_empty(list);
    puts("Please select the seat:");
    scanf("%d",&id);
    if (list[id - 1].booked) {
        puts("Error selected.");
        return;
    }
    list[id - 1].id = id;
    puts("Please input you FIRST_NAME LAST_NAME:");
    scanf("%s %s",fname,lname);
    strcpy(list[id - 1].fname, fname);
    strcpy(list[id - 1].lname, lname);
    list[id - 1].booked = 1;
    puts("Booked!");
}

void cancel_book(struct seat list[]) {
    show_booked(list);
    int id;
    puts("Please select the seat to cancel:");
    scanf("%d",&id);
    if (list[id - 1].booked == 0) {
        puts("Error selected.");
        return;
    }
    list[id - 1].id = 0;
    list[id - 1].booked = 0;
    list[id - 1].fname[0] = '\0';
    list[id - 1].lname[0] = '\0';
//    memset(list[id - 1].fname, '\0', strlen(list[id - 1].fname));
//    memset(list[id - 1].lname, '\0', strlen(list[id - 1].lname));

}

void print_all(struct seat list[]) {
    for (int i = 0; i < 12; i++) {
        printf("id=%d, booked=%d, name:%s %s\n",list[i].id, list[i].booked, list[i].fname, list[i].lname);
    }
    puts("done!\n");
}

int main(void) {
    char selected;
    int air_no;
    printf("Please select airplane No1(102,311,444,519):");
    scanf("%d",&air_no);
    while (air_no == 102 || air_no == 311 || air_no == 444 || air_no == 519) {
        while (getchar() ! = '\n')	continue;  //妙
        switch (air_no) {
            case 102:
                printf("Now you select Air %d:\n",air_no);
                air_no = 0;
                break;
            case 311:
                printf("Now you select Air %d:\n",air_no);
                air_no = 1;
                break;
            case 444:
                printf("Now you select Air %d:\n",air_no);
                air_no = 2;
                break;
            case 519:
                printf("Now you select Air %d:\n",air_no);
                air_no = 3;
                break;
            default:
                break;
        }
        
        show_menu();
        while ((selected = getchar()) != 'f') {
            while (getchar() ! = '\n')	continue;
            switch (selected) {
                case 'a':
                    get_empty(list[air_no]);
                    break;
                case 'b':
                    show_empty(list[air_no]);
                    break;
                case 'c':
                    show_booked(list[air_no]);
                    break;
                case 'd':
                    book_seat(list[air_no]);
                    break;
                case 'e':
                    cancel_book(list[air_no]);
                    break;
                default:
                    print_all(list[air_no]);
                    break;
            }
           
            show_menu();
        }
        printf("Please select airplane No1(102,311,444,519):");
        scanf("%d",&air_no);
    }
    puts("All done\n");
    
    return 0;
}

联合、枚举和函数指针

联合与结构体不同的是,联合能够在同一个内存空间中存储不同的数据类型。即使联合内包含多个成员,一次也只能存储和使用一个成员。

枚举类型本质上是以符号名称来代替整型常量的一种方式,默认情况下,枚举常量从0开始赋予每一个列表中的常量,也可以单独指定赋值。

函数名就代表函数的地址,可以声明一个指向函数的指针,也可以把函数的地址作为参数传递给其他函数。函数指针在声明中必须指定指针指向的函数的类型,即函数的返回值和函数的参数,如void (*pf) (char *),定义了一个函数指针,指向的函数的返回值为void,函数的参数为char *.

#include <stdio.h>

double sum(double, double);
double diff(double, double);
double times(double, double);
double divide(double, double);

//double (*pf[4]) (double, double) = {sum, diff, times, divide};  //第一种定义方式

typedef double (*ptype) (double, double);                       //第二种定义方式
ptype pf[4] = {sum, diff, times, divide};
/*这两种方式虽然代码量上不同,但是最终被编译器编译后他们的逻辑、效率以及占用的内存都是一致的
使用 typedef 声明函数指针不是必须的,但这样做有很多好处,typedef (*pf)类比typedef struct {...}mystruct*/

int main(void) {
    pf[1] (10.0, 2.5);           //数组调用方式1
    (*pf[1]) (10.0, 2.5);        //数组调用方式2
    (*(pf + 1)) (10.0, 2.5);     //指针调用方式
   /*无论是直接使用函数指针变量调用函数,还是先解引用函数指针再调用函数,编译器都会将其正确地解析为对函数的调用*/
    return 0;
}

函数指针:

#include <stdio.h>
#include <math.h>

#define LENGTH 10

void transform(double src[], double tar[], int n, double (*func)(double)) {
    for (int i = 0; i < n; i++) {
        tar[i] = func(src[i]);
    }
}

int main(void) {
    double source[LENGTH], target[LENGTH];
    for (int i = 0; i < LENGTH; i++) {
        source[i] = i;
    }
    printf("The source data is:\n");
    for (int i = 0; i < LENGTH; i++) {
        printf("%g\t",source[i]);
    }
    printf("\n");
    
    transform(source, target, LENGTH, sin);
    
    printf("The target sin data is:\n");
    for (int i = 0; i < LENGTH; i++) {
        printf("%g\t",target[i]);
    }
    printf("\n");
    
    transform(source, target, LENGTH, cos);
    
    printf("The target cos data is:\n");
    for (int i = 0; i < LENGTH; i++) {
        printf("%g\t",target[i]);
    }
    printf("\n");
    
    return 0;
}

位操作

位字段是通过一个结构,在signed int 或者 unsigned int 类型变量中的相邻位上实现的标签设计,例如

struct {
  unsigned int autfd : 1;
  unsigned int bldfc : 2;
} ptnt;

位字段的使用与结构体类似,但是其中每一个成员都以位来表示。可以使用prnt.autfd的结构成员表示形式进行读写操作,但是这个成员是1位数据,因此只能表达0和1两种状态,赋值不能超过其取值范围。参考文章

掩码是指通过二进制的位操作隐藏或者关闭某些数位上的数据,如0111 1111可以在使用掩码按位与操作中隐藏最高位。

二进制字符串转成十进制数值:

#include <stdio.h>

int bstoi(char *str) {
    int result = 0;
    
    while (*str != '\0') {
        result *= 2;
        result += (*str++ - '0');
    }
    
    return result;
}

char* itobs(int n, char *ps) {
  int i;
  
}

int main(void) {
    char str[9];
    scanf("%s",str);
    printf("%d\n",bstoi(str));
    
    return 0;
}

十进制数值转成二进制字符串

#include <stdio.h>
#include <limits.h>

int bstoi(char *str) {
    int result = 0;
    
    while (*str != '\0') {
        result *= 2;
        result += (*str++ - '0');
    }
    
    return result;
}

void itobs(int n, char *ps) {
    const static int size = CHAR_BIT * sizeof(int);
    //CHAR_BIT:limits.h中的宏,指的是char类型中的位数(8)
    //sizeof(int) = 4(字节数)
    
    for (int i = size - 1; i >= 0; i--, n >>= 1) {
        ps[i] = (01 & n) + '0';
    }
    ps[size] = '\0';
}

int main(void) {
    int data;
    char str[33];
    scanf("%d",&data);
    itobs(data,str);
    printf("%s\n",str);  //将打印32个位
    
    return 0;
}

计算一个数中打开位(‘1’)的数量

int openbit_count(int n) {
    const static int size = 8 * sizeof(int);
    int cnt = 0;
    
    for (int i = size - 1; i >= 0; i--, n >>= 1) {
        if (n & 1) {
            cnt++;
        }
    }
    
    return cnt;
}

int main(void) {
    int number;
    scanf("%d",&number);
    printf("%d\n",openbit_count(number));
    
    return 0;
}

C预处理器和C库

#define除用于定义常量之外,还经常用于定义类对象宏类函数宏两种宏。在编译过程中,#define定义的常量在编译过程中直接被替换成定义的文本的过程称为宏展开。其中常量和类对象宏会被替换成**#define**定义的文本。类函数宏定义方式类似于函数,例如:

#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))

在类函数宏替换中可能会造成参数的替换歧义,因此在必要时使用足够多的圆括号来保证类函数宏运算的有效性和准确性。

头文件中包含了一些预处理指令、外部链接的数据变量以及函数的声明。条件编译的主要指令有:#ifdef#if#else#elseif#endif等。

内联函数(inline)是通过在编译、调用过程中用内联代码替换函数调用,实现快速调用函数的一种方式。

可变参数列表:

#include <stdio.h>
#include <stdarg.h> //valist
#include <stdlib.h> //malloc

double *new_arrary(int n, ...) {   //使用可变参数列表(variadic argument list)
    va_list ap;
    va_start(ap, n);
    
    double *array = (double *)malloc(n * sizeof(double));
    for (int i = 0; i < n; i++) {
        array[i] = va_arg(ap, double);
    }
    
    va_end(ap);
    return array;
}

void show_array(int n, double arr[]) {
    for (int i = 0; i < n; i++) {
        printf("%g\t",arr[i]);
    }
    putchar('\n');
}

int main(void) {
    double *p1, *p2;
    
    p1 = new_arrary(3,1.1,2.2,3.3);
    p2 = new_arrary(4,4.4,5.5,6.6,7.7);
    show_array(3,p1);
    show_array(4,p2);
    free(p1);
    free(p2);
    
    return 0;
}   

讲解:

double *new_array(int n,...)

函数名为 new_array,返回类型是 double *,即指向双精度浮点数的指针。参数列表中,第一个参数 n 是一个整数,用于指定数组的元素个数;... 表示可变参数列表,即可以传入任意数量的额外参数。

va_list ap;
va_start(ap, n);

va_list 是一个类型定义,用于存储可变参数列表的信息。ap 是一个 va_list 类型的变量,用于操作可变参数列表。va_start 宏用于初始化 ap,使其指向可变参数列表中的第一个参数。va_start 的第二个参数 n 是上述用于指定数组的元素个数的n。

for (int i = 0; i < n; i++) {
    array[i] = va_arg(ap, double);
}

使用一个 for 循环遍历数组,通过 va_arg 宏从可变参数列表中依次取出双精度浮点数,并将其赋值给数组的每个元素。va_arg 的第一个参数是 va_list 类型的变量 ap,第二个参数指定了要取出的参数类型,这里是 double

va_end(ap);

使用 va_end 宏清理 va_list 类型的变量 ap,释放相关资源。在使用完可变参数列表后,必须调用 va_end 以确保程序的正确性和资源的正确管理。

return array;

函数返回指向动态分配的双精度浮点数数组的指针。这个指针可以在调用函数中使用,例如传递给其他函数进行处理,或者在使用完毕后通过 free 函数释放内存。

高级数据表示

链表结构

程序设计中通常使用数组的形式来组织、管理大量相同类型的数据,但是数组形式不能灵活地拓展和修改元素数量(数组长度),只能在数组定义时声明固定数量的元素。为了动态和灵活地增加、删除数据,可以通过指针动态地管理元素。链表中的每一个元素称为一个节点,每个节点的结构内都包含一个指向下一个节点的指针。通过这些节点的指针,最终可以将这些节点元素链接在一起,这种数据形式被称为链表。结构一般如下:

struct film {
  char title[20];
  int rating;
  struct film *next;
};

队列结构

队列是一种特殊的链表形式。首先,队列中添加的新项只能添加到末尾。其次,从队列中删除元素只能从队列的头部删除。因此一般将队列的这种属性称为先进先出(First In First Out)。

二叉查找树

二叉查找树是一种结合了二分查找策略的链状结构,其中每一个节点内包含两个指向下一个节点(子节点)的指针,子节点又分为左节点和右节点,并按照这种规律链接下去。二叉查找树的节点一般定义如下:

/*定义树节点内容*/
typedef struct item {
    char petname[SLEN];
    char petkind[SLEN][SLEN];
} Item;

/*定义树节点*/
typedef struct trnode {
    Item item;
    struct trnode* left;   //同层节点关系
    struct trnode* right;
} Trnode;

/*定义树*/
typedef struct tree {
    Trnode *root;
    int size;
} Tree;

/*定义树节点的父子关系*/
typedef struct pair {
    Trnode *parent;				//不同层节点关系
    Trnode *child;
} Pair;

栈是链表系列的另一种数据形式。在栈中,只能在链表的一端添加和删除项,把项压入栈和从栈中弹出栈,是一种LIFO(Lsat In First Out)结构。

双向链表实现信息逆序遍历:

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

#define TSIZE 45

struct film {
    char title[TSIZE];
    int rating;
    struct film *next;
    struct film *pre;
};

char *s_gets(char *st, int n) {
    char *ret_val;
    char *find;
    //因为不确定输入长度同时需要对输入最大长度进行控制,所以使用fgets,但是如果输入长度在规定的长度以内会存储换行符
    ret_val = fgets(st, n, stdin);
    if (ret_val) {          //读取成功
        find = strchr(st, '\n');
        if (find) {         //找到换行符,换行符之前的内容就是输入的电影标题
            *find = '\0';   //将换行符替换成'\0',以返回完整的字符串
        } else {            //输入超过指定大小
            while (getchar() != '\n') { //清空输入缓冲区,返回被截断的字符串
                continue;
            }
        }
    }
    return ret_val;
}

int main(void) {
    struct film *head = NULL;
    struct film *prev, *current;
    char input[TSIZE];
    
    puts("Enter first movie title:");
    while (s_gets(input, TSIZE) != NULL && input[0] != '\0') {  //检查是否读取成功且用户输入的是不是空串
        current = (struct film*)malloc(sizeof(struct film));
        if (head == NULL) { //初始化链表头,只需操作一次
            head = current;
        } else {
            prev->next = current;
            current->pre = prev;
        }
        current->next = NULL;
        
        strcpy(current->title, input);  //把输入的电影标题存储到当前节点中
        puts("Enter your rating <0-10>");
        scanf("%d",&current->rating);
        while (getchar() != '\n') {  //清除缓冲区
            continue;
        }
        puts("Enter next movie title (empty line to stop)");
        //输入空行代表停止,因为输入空行不满足input[0] != '\0',可结束while循环
        prev = current;
    }
    
    /*显示电影列表*/
    if (head == NULL) {
        printf("No data entered.\n");
    } else {
        printf("Here is the movie list(bu sequence):\n");
    }
    current = head;
    while (current != NULL) {
        printf("Movie: %s Rating: %d\n",current->title,current->rating);
        current = current->next;
    }
    
    /*逆序显示电影列表*/
    puts("Here is the movie list(by inverted sequency):");
    current = prev;
    while (current != NULL) {
        printf("Movie: %s Rating: %d\n",current->title,current->rating);
        current = current->pre;
    }
    
    /*释放内存*/
    current = head;
    while (current != NULL) { //当current不是链表的末尾时
        head = current->next;
        //即将释放current所指向的节点,所以先要保存current下一个节点的位置防止后续节点丢失
        free(current);
        current = head; //为释放刚保存的节点做准备
    }
    printf("Bye!\n");
    
    return 0;
}

单向链表+递归实现信息逆序遍历:

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

#define TSIZE 45

struct film {
    char title[TSIZE];
    int rating;
    struct film *next;
};

char *s_gets(char *st, int n) {
    char *ret_val;
    char *find;
    //因为不确定输入长度同时需要对输入最大长度进行控制,所以使用fgets,但是如果输入长度在规定的长度以内会存储换行符
    ret_val = fgets(st, n, stdin);
    if (ret_val) {          //读取成功
        find = strchr(st, '\n');
        if (find) {         //找到换行符,换行符之前的内容就是输入的电影标题
            *find = '\0';   //将换行符替换成'\0',以返回不含'\n'的字符串
        } else {            //输入超过指定大小
            while (getchar() != '\n') { //清空输入缓冲区,返回被截断的字符串
                continue;
            }
        }
    }
    return ret_val;
}

void invert_show(struct film *ptr) {
    if (ptr->next != NULL) {
        invert_show(ptr->next);
    }
    //操作
    printf("Movie: %s Rating: %d\n",ptr->title,ptr->rating);
}

int main(void) {
    struct film *head = NULL;
    struct film *prev, *current;
    char input[TSIZE];
    
    puts("Enter first movie title:");
    while (s_gets(input, TSIZE) != NULL && input[0] != '\0') {  //检查是否读取成功且用户输入的是不是空串
        current = (struct film*)malloc(sizeof(struct film));
        if (head == NULL) { //初始化链表头,只需操作一次
            head = current;
        } else {
            prev->next = current;
        }
        current->next = NULL;
        
        strcpy(current->title, input);  //把输入的电影标题存储到当前节点中
        puts("Enter your rating <0-10>");
        scanf("%d",&current->rating);
        while (getchar() != '\n') {  //清除缓冲区
            continue;
        }
        puts("Enter next movie title (empty line to stop)");
        //输入空行代表停止,因为输入空行不满足input[0] != '\0',可结束while循环
        prev = current;
    }
    
    /*显示电影列表*/
    if (head == NULL) {
        printf("No data entered.\n");
    } else {
        printf("Here is the movie list(bu sequence):\n");
    }
    current = head;
    while (current != NULL) {
        printf("Movie: %s Rating: %d\n",current->title,current->rating);
        current = current->next;
    }
    
    /*逆序显示电影列表*/
    puts("Here is the movie list(by inverted sequency):");
    invert_show(head);
    
    /*释放内存*/
    current = head;
    while (current != NULL) { //当current不是链表的末尾时
        head = current->next;
        //即将释放current所指向的节点,所以先要保存current下一个节点的位置防止后续节点丢失
        free(current);
        current = head; //为释放刚保存的节点做准备
    }
    printf("Bye!\n");
    
    
    return 0;
}

自制栈的数据结构:

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

#define MAXSTACK 100

typedef char Item;
typedef struct stack {
    Item items[MAXSTACK];
    int top;
} Stack;

/*初始化栈*/
Stack *InitializeStack(void) {
    Stack *ps = (Stack *)malloc(sizeof(Stack));
    if (ps == NULL) {
        exit(EXIT_FAILURE);
    }
    ps->top = 0;
    return ps;
}

/*判断栈是否已满*/
bool StackIsFull(Stack *ps) {
    if (ps->top == (MAXSTACK - 1)) {
        return true;
    } else {
        return false;
    }
}

/*判断栈是否已空*/
bool StackIsEmpty(Stack *ps) {
    if (ps->top == 0) {
        return true;
    } else {
        return false;
    }
}

bool push(Item item, Stack *ps) {
    if (StackIsFull(ps)) {
        return false;
    }
    ps->items[ps->top + 1] = item;
    ps->top++;
    return true;
}

bool pop(Item *pitem, Stack *ps) {
    if (StackIsEmpty(ps)) {
        return false;
    }
    *pitem = ps->items[ps->top];
    ps->top--;
    return true;
}

void EmptyTheStack(Stack *ps) {
    if (ps != NULL) {
        free(ps);
    }
}

int main(void) {
    Stack *pstack;
    pstack = InitializeStack();
    
    char str[MAXSTACK];
    puts("Enter a string to test stack:");
    scanf("%s",str);
    
    int i = 0;
    while (!StackIsFull(pstack)) {
        if (str[i] != '\0') {
            push(str[i++], pstack);
        } else {
            break;
        }
    }
    
    Item ch;
    while (!StackIsEmpty(pstack)) {
        pop(&ch,pstack);
        printf("%c",ch);
    }
    printf("\n");
    
    EmptyTheStack(pstack);
    puts("Done!\n");
    
    return 0;
}

[完](#C Primer Plus 习题)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值