C语言基础

C语言基础

常量和宏模块

# include <stdio.h>

// 宏定义,相当于全局的静态变量,一改全改
# define URL "http://www.fishc.com"
# define NAME "鱼C工作室"
# define BOSS "小甲鱼"
# define YEAR 2021
# define MONTH 5
# define DAY 20

int main() {
	printf("%s成立于%d年%d月%d日\n", NAME, YEAR, MONTH, DAY); // 字符串类型的占位符
	printf("%s是%s创立的。。。\n", NAME, BOSS);
	printf("%s的域名是%s\n", NAME, URL);
	
	return 0;
} 

占位符的使用

# include <stdio.h>

int main() {
	int a;
	char b;
	float c;
	double d;
	
	a = 520;
	b = 'F';
	c = 3.14;
	d = 3.141592653;
	
	printf("鱼C工作室创办于2021年的%d\n", a); // %d int类型的占位符
	printf("I love %cishC.com!\n", b); // %c char类型的占位符
	printf("圆周率是:%.2f\n", c);  // %.2f float类型占位符
	printf("精确到小数点后9位的圆周率是:%11.9f\n", d); // %11.9f double 类型的占位符
	
	return 0;
}

基本类型

  • short int
  • int
  • long int
  • long long int

浮点数类型

  • float
  • double
  • long double

字符类型

  • char

布尔类型

  • _Bool

枚举类型

  • enum

sizeof 运算符

法一:sizeof(对象,类型) --> 推荐
法二:sizeof 对象,类型
# include <stdio.h>

int main() {
	
	printf("int = %d\n", sizeof(int));  // int = 4
	printf("short int = %d\n", sizeof(short));  // short int = 2
	printf("long int = %d\n", sizeof(long));  // long int = 4
	printf("long long int = %d\n", sizeof(long long));  // long long int = 8
	printf("char = %d\n", sizeof(char));  // char = 1 
	printf("_Bool = %d\n", sizeof(_Bool)) ; //_Bool = 1
	printf("float = %d\n", sizeof(float));  // float = 4
	printf("double = %d\n", sizeof(double));  // double = 8
	printf("long double = %d\n", sizeof(long double));  // long double = 16
	
	return 0;
}

signed和unsigned

signed(带符号) --> 默认
	- [signed] short [int]
	- 中括号可以去掉
unsigned(不带符号)
# include <stdio.h>

int main() {
	short i;
	unsigned short j;
	
	i = -1;
	j = -1; //这是定义了没有符号的,可是却写了-1所以结果会错 
	
	printf("%d\n", i); // -1   有符号(默认)为用 %d 占位
	printf("%u\n", j);  // 65535    无符号位用 %u 占位
	
	return 0;
}
# include <stdio.h> 
# include <math.h>

// 正数的源码、反码和补码都是一样的 

int main() {
	
	unsigned int result = pow(2, 32) - 1;
	printf("result = %u\n", result); // 无符号的用 %u 有符号用%d 
	 
	return 0;
}

字符串

char 变量名 [数量]

  • eg: 声明字符串
    char name[5]
  • eg:定义字符串
    char name[5] = {‘f’, ‘i’, ‘s’, ‘h’, ‘C’};
字符串要用 \0 进行结尾
# include <stdio.h> 

int main() {
	
	// 写法一 
	char a[6] = {'F', 'i', 's', 'h', 'C', '\0'};
	
	printf("%s\n", a);
	printf("Hello\n");
	
	// 写法二(推荐)
	char b[] = "FishC_2";
	printf("%s\n", b);
	printf("Hello\n");
	
	return 0;	
}

算数运算符

  • int / int 只保留整数部分,小数部分直接舍弃 eg: 5 / 3 = 1
  • +5 = 5
  • -5 = -5
  • pow(a, b) 计算a的b次方

优先级

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

int main() {
	
	int i, j, k;
	
	i = 1 + 2;
	j = 1 + 2 * 3;
	k = i + j + -1 + pow(2, 3); // 3 + 7 + (-1) + 2^3 = 17 
	
	printf("i = %d\n", i);
	printf("j = %d\n", j);
	printf("k = %d\n", k);
	
	return 0;
} 

数据类型转换

# include <stdio.h>

int main() {
	
	printf("整形输出:%d\n", 1 + 2.0); // 0
	printf("整形输出:%d\n", 1 + (int)2.0); //强制数据类型转换  3
	printf("整形输出:%d\n", 1 + (int)1.8); //强制数据类型转换 ,会直接舍弃小数位 
	printf("浮点型输出:%f\n", 1 + 2.0); // 浮点型用 %f  
	
	return 0; 
}

关系运算符

  • 1 为真
  • 0为假

逻辑运算符

  • &&
  • ||
# include <stdio.h>

int main() {
	int a = 5;
	int b = 3;
	
	printf("%d\n", 3 > 1 && 1 < 2); // 1
	printf("%d\n", 3 + 1 || 2 == 0); // 1
	printf("%d\n", !(a + b)); // 0
	printf("%d\n", !0 + 1 < 1 || !(3 + 4)); // 0
	printf("%d\n", 'a' - 'b' && 'c'); // 1
	
	return 0;
} 
短路与、或 求值
# include <stdio.h>

int main() {
	int a = 3;
	int b = 3;
	
	(a = 0) && (b = 5); // 前面为假,所以直接短路,不看 && 后面的内容了 
	printf("a = %d, b = %d\n", a, b); // a = 0, b = 3
	
	(a = 1) || (b = 5); // 前面为真,所以直接短路,不看 || 后面的内容了 
	printf("a = %d, b = %d\n", a, b); // a = 1, b = 3
	
	return 0;
}

小算法

# include <stdio.h>

int main() {
	// 计算 1 到 100 的和
	
	// 方法一 for 
	int i;
	int sum = 0;
	int n = 100;
	
	for(i = 1; i <= n; i++){
		sum += i;
	}
	
	printf("%d\n", sum);
	
	// 方法二 算法 (推荐) 
	int i_1 = 1;
	int sum_1 = 0;
	int n_1 = 100;
	
	sum_1 = (i_1 + n_1) * n_1 / 2;
	
	printf("%d\n", sum_1);
	return 0;
}

if语句

  • scanf(“%d”, &i); // 接受用户输入的数据,然后赋值给 i
# include <stdio.h>

int main() {
	
	int i;
	
	printf("您老贵庚啊:");
	scanf("%d", &i);  // 接受用户输入的数据,然后赋值给 i
	
	if(i >= 18 && i < 50) {
		printf("进门左拐!\n");
	} else if(i >= 50) {
		printf("进门右转!\n");
	} else {
		printf("您请回吧。\n");
	}
	
	return 0; 
} 
# include <stdio.h>

int main() {
	
	int i;
	printf("请输入成绩:");
	scanf("%d", &i); 
	
	
	if(i >= 90) {
		printf("A");
	} else if(i < 90 && i >= 80) {
		printf("B");
	} else if(i < 80 && i >= 70) {
		printf("C");
	} else if(i < 70  && i >= 60) {
		printf("D");
	} else {
		printf("E");
	}
	
	return 0;
}
# include <stdio.h>

int main() {

    int a,b;
    printf("请输入两个数字");
    scanf("%d %d", &a, &b);

    if(a != b) {
        if(a > b) {
            printf("%d > %d\n", a, b);
        } else {
            printf("%d < %d\n", a, b);
        }
    } else {
        printf("%d = %d\n", a, b);
    }

    return 0;
}
  • getchar(); //去除回车,避免回车造成的第二次输入错误
# include <stdio.h>

int main() {

    char isFree,isRain;

    printf("是否有空?(Y/N)");
    scanf("%c", &isFree);

    getchar(); //去除回车,避免回车造成的第二次输入错误

    printf("是否下雨?(Y/N)");
    scanf("%c", &isRain);

    if(isFree == 'Y') {
        if(isRain == 'Y') {
            printf("记得带伞哦。");
        } else {
            printf("大胆去约会吧!");
        }
    } else {
        printf("女神没空。");
    }
    return 0;
}

switch语句

# include <stdio.h>

int main() {
	
	char ch;
	printf("请输入成绩:");
	scanf("%c", &ch);
	
	switch (ch) {
		case 'A': 
			printf("你的成绩在90分以上\n");
		break;
		 case 'B': 
			printf("你的成绩在80到90之间\n");
		break;
		case 'C': 
			printf("你的成绩在70到80之间\n");
		break;
		case 'D': 
			printf("你的成绩在60到70之间\n");
		break;
		case 'E': 
			printf("你的成绩小于60分");
		break;
		default:
			printf("请输入正确的成绩等级");
		break; 	
	}
	
	
	
	return 0; 
}

while语句

# include <stdio.h>

int main() {
	int i =1, sum = 0;
	while(i <= 100) {
		sum += i;
		i++;
	}
	printf("结果是:%d\n", sum);
	
	return 0;
}
# include <stdio.h>

int main() {

    int count = 0;
    printf("请输入一行英文字符:");

    while (getchar() != '\n')
    {
        count++;
    }
    printf("你总共输入了%d个字符\n",count);
    
    return 0;
}

for循环

# include <stdio.h>

int main() {
    
    int num;
    _Bool flag = 1;

    printf("请输入一个整数");
    scanf("%d", &num);

    for (int i = 2; i < num / 2; i++) // c99 标准下才可以这样写,否则直接能把int i 的定义写道外面去
    {
        if(num % i == 0) {
            flag = 0;
        }
    }
    
    if (flag)
    {
        printf("%d是一个素数\n", num);
    } else {
        printf("%d不是速素数\n", num);
    }
    

    return 0;
}
# include <stdio.h>

// 打印九九乘法表
int main() {
    for (int i = 1; i <= 9; i++)
    {
        for (int j = 1; j <= i; j++)
        {
           printf("%d * %d = %-2d  ", i, j, i * j); // %-2d 也是占位符,用来存储比较大的数字
        }
        printf("\n");
    }
    
}
  • break
# include <stdio.h>

int main() {
    
    long long num;
    _Bool flag = 1;

    printf("请输入一个整数");
    scanf("%d", &num);

    for (long long i = 2; i < num / 2; i++)// c99 标准下才可以这样写,否则直接能把int i 的定义写道外面去
    {
        if(num % i == 0) {
            flag = 0;
            break; // 减少比必要的代码执行,得到代码的优化
        }
    }
    
    if (flag)
    {
        printf("%lld是一个素数\n", num); // %lld long long 类型的占位符
    } else {
        printf("%lld不是一个素数\n", num);
    }
    

    return 0;
}
  • continue
# include <stdio.h>

int main() {

    int ch;

    printf("请输入一串包含C的字符:");

    while ((ch = getchar()) != '\n')
    {
        if (ch == 'C')
        {
           continue;
        }
        putchar(ch);  // 用putchar来打印getchar() 的结果
    }
    
    putchar('\n'); // 可以用 '' 输出字符来代替printf()
    return 0;
}

数组

  • 定义 类型 数组名 [元素个数(常量或者是常量表达式)]
    eg:
    • int a[6];
    • char b[24];
    • double c[3];
    • int a[10] = {0};
# include <stdio.h>

// 宏定义
#define NUM 10

int main() {
    int s[NUM];
    int i, sum = 0;

    for (i = 0; i < 10; i++)
    {
        printf("请输入地%i位同学的成绩:", i+ 1); // %i 类似 %d 一般使用%d
        scanf("%d",&s[i]); // & 赋值符号
        sum += s[i];
    }

    printf("成绩录入完毕,该次考试的平均分是%.2f\n", (double)sum / NUM);
    

    return 0;
}
# include <stdio.h>

int main() {
    int a[10] = {1, 2, 3, 4, 5}; // 可以不赋值全部的元素,其他的为零
    int b[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0} ; // 有初始化的数组可以不写长度
    int c[10] = {[0] = 0, [5] = 5, [8] = 8}; // c99 标准下可以给单独指定的元素赋值

    int i;
    for (i = 0; i < 10; i++)
    {
        printf("%d  ", a[i]);
    }
    printf("\n");
    for (i = 0; i < 10; i++)
    {
        printf("%d  ", b[i]);
    }
    printf("\n");
    for (i = 0; i < 10; i++)
    {
        printf("%d  ", c[i]);
    }
    printf("\n");

    printf("%d", sizeof(a)); // sizeof 会打印出数组所占的内存

    return 0;
}

c99 标准下动态定义的数组

# include <stdio.h>

int main() {

    int i, n;
    printf("请输入要输入的字符串长度:");
    scanf("%d", &n);

    char c[n + 1];

    printf("现在开始输入字符串:");
    getchar(); // 把多余的回车拿掉
  
    for (i = 0; i < n; i++)
    {
        scanf("%c", &c[i]);
    }
    c[n] = '\0'; // 字符串以 \0 结尾

    printf("输入的字符串是:%s\n", c);  //直接写数组的名字
    
    return 0;
}

字符串的处理函数

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

int main() {
    //1. 获取字符串的长度 strlen
    char str[] = "I love FishC.com!";

    printf("sizeof str = %d\n", sizeof(str)); // 18
    printf("strlen str = %u\n", strlen(str)); // 17


    //2.  拷贝字符串 strcpy 和 strncpy
    char str1[] = "Original String";
    char str2[] = "New String";
    char str3[100];

    strcpy(str1, str2);  // strcpy(源字符串, 要被复制过去的字符串); --> 源字符串的长度 要大于 被复制过去的字符串 的长度,否则就会发生溢出
    strcpy(str3, "Copy Successful");

    printf("str1:%s\n", str1);
    printf("str2:%s\n", str2);
    printf("str3:%s\n", str3);


    char str4[] = "To be or not to be";
    char str5[40];

    strncpy(str5, str4, 5); // 三个参数,防止字符串的溢出, 不包括最后的结束符号
    str5[5] = '\0'; // 字符串以 '\0' 结尾,所以最后必须加一个 '\0'

    printf("str5:%s\n", str5);

    //3.  链接字符串 strcat 和 strncat
    char str6[] = "I love";
    char str7[] = "FishC.com!";

    strcat(str6, " ");  // 链接一个空格
    strcat(str6, str7);  // 在连接一个字符串

    printf("str6: %s\n", str6);
    // strncat 也是三个参数,然后在后面自动添加一个结束字符

    //4.  比较字符串 strcmp 和 strncmp
    // 如果两个字符串相等 则返回 0  -->  主要通过 ASCII码来比较

    char str8[] = "FichC.com!";
    char str9[] = "Fichc.com!";

    if (!strcmp(str8, str9))
    {
        printf("两个字符串,完全一致");
    } else
    {
        printf("两个字符串,存在差异");
    }

    return 0;
}

二维数组

  • 类型 数组名 [常量表达式][常量表达式]
# include <stdio.h>

int main() {
    int a[3][4] = {
        {1, 2, 3, 4}, 
        {5, 6, 7, 8}, 
        {9, 10, 11, 12}
    };

    // 第一个维度可以不写,其他的都必须写
    int b[][4] = {0};

    int i,j;

    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 4; j++)
        {
            printf("%d ", a[i][j]);
        }
        printf("\n");
    }
    
    return 0;
}
矩阵的转置
# include <stdio.h>

int main() {

    int i,j;
    int a[4][5] = {
        {80, 92, 85, 86, 99},
        {78, 65, 89, 70, 99},
        {67, 78, 76, 89, 99},
        {88, 68, 98, 90, 99}
    };

    printf("原矩阵:\n");
    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 5; j++)
        {
            printf("%d ",a[i][j]);
        }
        printf("\n");
    }
    
    printf("转置后的矩阵:\n");
      for (i = 0; i < 5; i++) //转置的时候交换一二维数组的长度
    {
        for (j = 0; j < 4; j++)
        {
            printf("%d ",a[j][i]); // 交换i和j的位置
        }
        printf("\n");
    }

    return 0;
}

指针(重点中的重点)

指针和指针地址
- 地址 常称为指针
- 存放地址的变量 常称为指针变量
  • 类型 *指针变量
取地址运算符 和 取值运算符
- 如果需要获取某个变量的地址,可以使用取地址运算符(&):
	char *pa = &a;
	int *pb = &f;

- 如果需要访问指针变量指向的数据,可以使用取值运算符(*): // 这里的*和定义指针时用的*是一样的
	printf("%c, %d\n", *pa, *pb);

- 避免访问未初始化的指针
# include <stdio.h> 

int main() {

    char a = 'F';
    int f = 123;

    char *pa = &a; // 定义指针,--> * 是定义指针 --> & 是取到相应的地址
    int *pb = &f;

    printf("a = %c\n", *pa);  // 这里 * 的意思不再只是定义指针,而是取值运算符
    printf("b = %d\n", *pb);


    // 通过指针间接访问变量
    *pa = 'C';
    *pb += 1;

    printf("now,a = %c\n", *pa); 
    printf("now,b = %d\n", *pb);

    printf("sizeof pa = %d\n", sizeof(pa)); // 指针存放的地址都是一样的,因为指针里存放的就是地址
    printf("sizeof pb = %d\n", sizeof(pb));

    printf("the addr of a is %p\n", pa); // 打印地址,%p 是字符串类型
    printf("the addr of a is %p\n", pb);

    return 0;
}

指针和数组

# include <stdio.h>

int  main() {

    int a;
    int *p = &a;
    printf("请输入一个整数:");
    scanf("%d", &a);
    printf("a = %d\n", a);

    printf("请重新输入一个整数:");
    scanf("%d", p); // 指针p指向的就是a的地址所以不用加  & (取地址运算符)
    printf("a = %d\n", a);

    return 0;
}
  • 数组名其实就是一个地址信息,也是第一个数组元素的地址, 不需要用 & 即可直接访问
# include <stdio.h>

int main() {

    char str[128];

    printf("请输入鱼C工作室的域名:");
    scanf("%d", str); //数组名不用加 & 

    // printf("鱼C工作室的域名是:%s\n", str);

    // 打印出两个一样的地址
    printf("str 的地址是:%p\n", str); 
    printf("str 的地址是:%p\n", &str[0]); // 第二个数是第一个数的地址加上该类型的一个元素的长度

    return 0;
}
  • p+1 并不是简单的讲指针的地址加1,而是指向数组的下一个元素
# include <stdio.h>

int main() {

    int b[5] = {1, 2, 3, 4, 5};

    int *p = b;

	// p+1 并不是简单的讲指针的地址加1,而是指向数组的下一个元素
    printf("*p = %d, *(p + 1) = %d, *(p + 2) = %d\n", *p, *(p+1), *(p+2));

	// 用指针法来直接访问数组元素,直接用 数组名 加一
    printf("*b = %d, *(b + 1) = %d, *(b + 2) = %d\n", *b, *(b+1), *(b+2));

    return 0;
}
  • 利用指针的形式定义字符指针型变量 字符串,然后利用数组下标的形式访问字符串
# include <stdio.h>
# include <string.h>

// 利用指针的形式定义字符指针型变量 字符串,然后利用数组下标的形式访问字符串
int main() {
    // 利用指针的形式定义字符指针型变量 字符串
    char *str = "I love FishC.com";
    int i, length;

	// 定义一个变量得到字符串长度的值,而不是直接放在for循环里面,可以避免函数的重复调用,节省资源
    length = strlen(str); 

	// 利用数组下标的形式访问字符串
    for (i = 0; i < length; i++)
    {
        printf("%c",  str[i]);
    }
    printf("\n");

    return 0;
}

指针数组和数组指针

  • 数组名只是一个地址,而指针是一个左值 lvalue
  • C 语言的术语 lvalue 指用于识别或定位一个存储位置的标识符。(注意:左值同时还必须是可改变的)
# include <stdio.h>

int main() {

    char str[] = "I love FishC.com!";
    // 不可直接用字符串的名字 ++ 因为自增运算符要求是左值
    char *target = str; // 加入指针,指向字符串
    int count = 0;

    // while(*str++ != '\0') 错误写法,要求左值 lvalue
    while(*target++ != '\0') { // 这里的 * 是取值运算符
        count++;
    }

    printf("共有%d个字符!\n", count);

    return 0;
}
  • 指针数组 --> int *p1[5];
    • 指针数组是一个数组,每个数组元素存放一个指针变量
# include <stdio.h>

int main() {
	
	// 指针数组
    char *p1[5] = {
        "让编程改变世界",
        "Just To Do it",
        "永不止步",
        "一切皆有可能",
        "One more thing"
    };

    int i;

    for (i = 0; i < 5; i++)
    {
        printf("%s\n", p1[i]);
    }
    

    return 0;
}
  • 数组指针 --> int (*p2)[5];
  • 数组指针是一个指针,它指向的是一个数组
# include <stdio.h>

int main() {
    // 数组指针
    // 指针在给值的时候要给地址才行
    int temp[] = {1, 2, 3, 4, 5};
    int (*p2)[5] = &temp; // 利用 & 取出数组的地址,直接赋值给指针数组
    //如果只是给一个指针赋值,则可以如下写法,表示把数组的第一个值的地址给指针
    // int *p = temp; 
    int i;

    for (i = 0; i < 5; i++)
    {
        printf("%d\n", *(*p2 + i)); // 第一个 * 取值, 第二个 * 表示指针
    }
    
    return 0;
}

指针和二维数组

  • 二维数组名代表的名字代表的是第一个一维数组的地址
# include <stdio.h>

int main() {

    int array[4][5] = {0};

    printf("int sizeof is = %d\n", sizeof(int));
    printf("array: %p\n", array);
    printf("array + 1 :%p\n", array + 1); // 得到的值是14(H)=10(D) 所以是一个5个int的一维数组

    return 0;
}
  • 取出地址的值叫做 “解引用”,
  • *(array)
# include <stdio.h>

int main() {

    int array[4][5] = {0};
    int i,j,k = 0;

    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 5; j++)
        {
            array[i][j] = k++;
        }
        
    }
    
    printf("*(array + 1): %p\n", *(array + 1));
    printf("array[1]: %p\n", array[1]);
    printf("&array[1][0]: %p\n", array[1][0]);
    printf("**(array + 1): %d\n", **(array + 1));

    return 0;
}
  • 二维数组与数组指针
# include <stdio.h>

int main() {

    int array[2][3] = {{0, 1, 2}, {3, 4, 5}};
    int (*p)[3] = array; // 数组指针
	
	// 不同索引的不同表现方式,  值相同
    printf("**(p + 1): %d\n", **(p + 1));
    printf("**(array + 1): %d\n", **(array + 1));
    printf("array[1][0]: %d\n", array[1][0]);
    
    //分界线
    printf("======================\n");
	
	// 不同索引的不同表现方式, 值相同
    printf("*(*(p + 1) + 2): %d\n", *(*(p + 1) + 2));
    printf("*(*(array + 1) + 2): %d\n", *(*(array + 1) + 2));
    printf("array[1][2]: %d\n", array[1][2]);

    return 0;
}

void指针 和 NULL指针

void
  • void指针我们把它称为通用指针,就是可以指向任何类型的数据。也就是说,任何类型的指针都可以赋值给void指针。
# include <stdio.h>

// 其他指针转换成 void ,直接转换
// void 转换成其他类型,强制类型转换

int main() {

    int num = 1024;
    int *pi = &num;
    char *ps = "FishC";
    void *pv;

    pv = pi; // 赋值int类型
    printf("*pi: %p, *pv: %p\n", pi, pv);
    //void 转换成其他类型,强制类型转换
    printf("*pv: %d\n", *(int *)pv);

    pv = ps; // 赋值char类型
    printf("*ps: %p, *pv: %p\n", ps, pv);
    //void 转换成其他类型,强制类型转换
     printf("*pv: %s\n", (char *)pv);
    return 0;
}
NULL
  • define NULL((void *) 0)

  • 当你还不清楚要将指针初始化为什么 地址时,请将它初始化为NULL;在对指针进行解引用时,先检查指针是或否为NULL。这种策略可以为你今后编写大型程序节省大量的调试时间。
# include <stdio.h>

int main() {
    // 不对指针进行初始化,就会出现指针乱指的情况,容易使程序出错,不容易排查
    int *p1; 
    int *p2 = NULL;

    printf("*p1: %d\n", *p1);
    printf("*p2: %d\n", *p2);

    return 0;
}

指向指针的指针

# include <stdio.h>

int main() {

    int num = 520;
    int *p = &num;
    int **pp = &p;

    printf("num: %d\n", num);
    printf("*p: %d\n", *p);
    printf("**pp: %d\n", **pp);

    printf("&p: %p, pp: %p\n", &p, pp); // &p, pp 是 p 的地址
    printf("&num: %p, p: %p, *pp: %p\n", &num, p, *pp);  // &num, p, *pp 是 num的地址

    return 0;
}
  • 指向指针的指针来指向数组指针
# include <stdio.h>

int main() {

    char *cBooks[] = { // 指针数组,是数组存放指针变量
        "《C程序设计语言》",
        "《C专家编程》",
        "《C和指针》",
        "《C陷阱与缺陷》",
        "《C Primer Plus》",
        "《带你学C带你飞》"
    };

    char **byFishC;
    char **jiayuLoves[5];

    // byFishC 指向字符指针的指针 的变量
    // cBooks[5] 就是得到第五个的值 《带你学C带你飞》
    // &cBooks[5] 得到 cBooks[5] 的地址值
    byFishC = &cBooks[5]; 
    int i;

    jiayuLoves[0] = &cBooks[0];
    jiayuLoves[1] = &cBooks[1];
    jiayuLoves[2] = &cBooks[2];
    jiayuLoves[3] = &cBooks[3];
    jiayuLoves[4] = &cBooks[4];
    

    printf("FishC出版的图书有:%s\n", *byFishC);
    printf("小甲鱼喜欢的书有:\n");

    for (i = 0; i < 5; i++)
    {
        printf("%s\n", *jiayuLoves[i]);
    }
    
    return 0;
}

常量和指针

  • const 可以把变量变成常量,不可以再修改,只可以读取其中的值(类似于Java中的final关键字)
  • 指针可以修改为指向不同的常量
  • 指针可以修改为指向不同的变量
  • 可以通过解引用来读取指针指向的数据
  • 不可以通过解引用修改指针指向的数据
# include <stdio.h>

int main() {

    const float pi = 3.1415926; // const 把变量变成常量
    printf("%f\n", pi);

    return 0;
}
  • 指向常量的指针
# include <stdio.h>

int main() {

    int num = 520;
    const int cnum = 880; // 不可修改
    const int *pc = &cnum; // 不可修改

    printf("cnum: %d, &cnum: %p\n", cnum, &cnum);
    printf("*pc: %d, pc: %p\n", *pc, pc);

    // *pc = 1024; 解引用的形式会报错

    pc = &num; //指针可以修改地址
    printf("num: %d, &num: %p\n", num, &num);
    printf("*pc: %d, pc: %p\n", *pc, pc);

    num = 1024; // 通过修改num来改变指针指向的值
    printf("*pc: %d, pc: %p\n", *pc, pc);

    return 0;
}
  • 常量指针

– 指向非常量的常量指针

  • 指针不可以被修改
  • 指针指向的值可以被修改

– 指向常量的常量指针

  • 指针自身不可以修改
  • 指针指向的值也不可以被修改

函数(一个封装的方法)

函数的类型就是函数的返回值类型

函数名相当于函数的地址

# include <stdio.h>

void print_C(); // 声明函数,不返回任何数据,默认是int类型
// 如果不做声明,那么就必须把 函数的定义 写在 函数的调用 前面

void print_C() { // 定义函数
    printf(" ###### \n");
    printf("##    ##\n");
    printf("##      \n");
    printf("##      \n");
    printf("##      \n");
    printf("##    ##\n");
    printf(" ###### \n");
}

int main() {
    print_C(); // 调用函数
    return 0;
}

函数的参数和返回值

  • 计算"1+2+3+…+(n-1)+n的结果
# include <stdio.h>

int sum(int n);

int sum(int n) {
    int result = 0;

    do
    {
        result += n;
    } while (n-- > 0);

    return result;
    
}

int main() {

    int n;
    printf("请输入n的值:");
    scanf("%d", &n);

    printf("1+2+3+......+(n-1)+n的结果是:%d\n", sum(n));

    return 0;
}
  • 打印两个整数中的较大值
# include <stdio.h>

int max(int, int);

int max(int x, int y) {
    if (x > y)
    {
        return x;
    } else
    {
        return y;
    }
}

int main() {

    int a, b, c;

    printf("请输入两个整数,用空格分隔:");
    scanf("%d%d", &a, &b);

    c = max(a, b);
    printf("他们中较大的值是:%d\n", c);

    return 0;
}
传值和传址
  • 传值:传入普通数值
# include <stdio.h>

void swap(int x, int y);

void swap(int x, int y) {
    int temp;

    printf(" In swap互换前:x = %d, y = %d\n", x, y);

    temp = x;
    x = y;
    y = temp;

    printf(" In swap互换后:x = %d, y = %d\n", x, y);
}

int main() {

    int x = 3, y = 5;

    printf(" In main互换前:x = %d, y = %d\n", x, y);
    swap(x, y);
    printf(" In main互换后:x = %d, y = %d\n", x, y);

    return 0;

    /*
        运行结果
        In main互换前:x = 3, y = 5
        In swap互换前:x = 3, y = 5
        In swap互换后:x = 5, y = 3
        In main互换后:x = 3, y = 5
    */
}
  • 传址:传入地址
# include <stdio.h>

void swap(int *x, int *y); // 参数是指针,指针里面放的是地址

void swap(int *x, int *y) {
    int temp;

    printf(" In swap互换前:x = %d, y = %d\n", *x, *y); // 对指针进行解引用

    temp = *x;// 对指针进行解引用
    *x = *y;// 对指针进行解引用
    *y = temp;// 对指针进行解引用

    printf(" In swap互换后:x = %d, y = %d\n", *x, *y);
}

int main() {

    int x = 3, y = 5;

    printf(" In main互换前:x = %d, y = %d\n", x, y);
    swap(&x, &y); // 参数应该传入地址
    printf(" In main互换后:x = %d, y = %d\n", x, y);

    return 0;

    /*
        运行结果
        In main互换前:x = 3, y = 5
        In swap互换前:x = 3, y = 5
        In swap互换后:x = 5, y = 3
        In main互换后:x = 5, y = 3
    */
}
传数组
# include <stdio.h>

void get_array(int a[10]);

void get_array(int a[10]) {
    int i;

    // 改变这个位置的元素,之后调用数组的时候都会被改动
    a[5] = 520; 

    for (i = 0; i < 10; i++)
    {
        printf("a[%d] = %d\n", i, a[i]);
    }
    
}

int main() {
    int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int i;

    // 不是传的整个数组,而是传递的第一个元素的地址
    get_array(a); 

    printf("在main函数里面再打印一次...\n");
    for (i = 0; i < 10; i++)
    {
        printf("a[%d] = %d\n", i, a[i]);
    }

    return 0;
}
# include <stdio.h>

void get_array(int b[10]);

void get_array(int b[10]){
    printf("sizeof b: %d\n", sizeof(b));
}

int main() {
    
    int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

    printf("sizeof a: %d\n", sizeof(a));  // 40
    get_array(a);  // 8

    return 0;
}
可变参数
  • include <stdarg.h>

    • va_list
    • va_start
    • va_arg
    • va_end
# include <stdio.h>
# include <stdarg.h>

int sum(int n, ...); // 三个点说明参数的个数不确定

int sum(int n, ...) {

    int i,sum = 0;
    va_list vap; // 定义参数列表,定义字符指针

    va_start(vap, n); // 初始化宏
    for (i = 0; i < n; i++)
    {
        sum += va_arg(vap, int); // 获取到参数的值 和 类型
    }
    va_end(vap); // 结束宏

    return sum;
}

int main() {

    int result;

    // 第一个参数表示有几个变量,后面的参数是对应的变量
    result = sum(3, 1, 2, 3); 
    printf("result = %d\n", result);

    result = sum(5, 1, 2, 3, 4, 5);
    printf("result = %d\n", result);

    result = sum(7, 1, 2, 3, 4, 5, 6, 7);
    printf("result = %d\n", result);

    return 0;
}

指针函数[ int *p() ]和函数指针[ int (*p)() ]

指针函数[ int *p() ] --> 不要返回局部变量的指针
# include <stdio.h>

// 指针函数
// 使用指针变量作为函数的返回值,就是指针函数
char *getWord(char);
char *getWord(char c) { // char类型的指针
    switch (c)
    {
        case 'A': return "Apple";
        case 'B': return "Banana";
        case 'C': return "Cat";
        case 'D': return "Dog";
        default: return "None";
    }
}

int main() {
    char input;

    printf("请输入一个字母:");
    scanf("%c", &input);

    printf("%s\n", getWord(input));

    return 0;
}
函数指针[ int (*p)() ]
# include <stdio.h>

int square(int);
int square(int num) {
    return num * num;
}

int main() {

    int num;
    int (*fp)(int); // 函数指针
	// 上面的函数指针拥有整形返回值,整形的参数,所以可以之直接等于square函数

    printf("请输入一个整数:");
    scanf("%d", &num);

    // 函数名相当一函数的地址,可以直接写
    fp = square; 

	// & 取址运算符
    // 写法二:fp = &square; 

    printf("%d * %d = %d", num, num, (*fp)(num));
    // 写法二:printf("%d * %d = %d", num, num, fp(num));

    return 0;
}
函数指针作为参数
# include <stdio.h>

int add(int, int);
int sub(int, int);
// 第一个参数,是一个函数指针,表示一个地址(一个函数)
// int (*fp)(int, int) 一个地址,具有这样的属性
int calc(int (*fp)(int, int), int, int);

int add(int num1, int num2) {
    return num1 + num2;
}

int sub(int num1, int num2) {
    return num1 - num2;
}

int calc(int (*fp)(int, int), int num1, int num2) {
    return (*fp)(num1, num2); // 返回一个函数指针(调用的函数)
}

int main() {

    printf("3 + 5 = %d\n", calc(add, 3, 5));
    printf("3 - 5 = %d\n", calc(sub, 3, 5));

    return 0;
}
函数指针作为返回值
# include <stdio.h>

int add(int, int);
int sub(int, int);
int calc(int (*)(int, int), int, int);
int (*select(char op))(int, int);

int add(int num1, int num2) {
    return num1 + num2;
}

int sub(int num1, int num2) {
    return num1 - num2;
}

int calc(int (*fp)(int, int), int num1, int num2) {
    return (*fp)(num1, num2);
}

int (*select(char op))(int, int) {
    switch(op) {
        case '+': return add;
        case '-': return sub;
    }
}

int main() {

    int num1, num2;
    char op;
    int (*fp)(int, int);

    printf("请输入一个式子(如 1+3):");
    scanf("%d%c%d", &num1, &op, &num2);

    fp = select(op); // fp = select(op) 返回的是函数指针,所以用此来接收
    printf("%d %c %d = %d\n", num1, op, num2, calc(fp, num1, num2));
    return 0;
}

局部变量和全局变量

局部变量
# include <stdio.h>


int main() {

    int i = 520;

    printf("before, i = %d\n", i);

    for (int i = 0; i < 10; i++) // C99 标准
    {
        printf("for, i = %d\n", i);
    }
    
    printf("after, i = %d\n", i);

    return 0;
}
全局变量(外部变量)
  • 与局部变量不同,int类型的全局变量如果没有被赋值,会默认为 0;
# include <stdio.h>

void a();
void b();
void c();

int count = 0;

void a() {
    count++;
}
void b() {
    count++;
}
void c() {
    count++;
}

int main() {

    a();
    b();
    c();
    b();
    printf("小郭今天被抱了 %d 次!", count);

    return 0;
}
  • 局部变量和全局变量名字相同时不会报错。
# include <stdio.h>

void func();

// a 未赋值,默认为0, b 赋值为 520
int a, b = 520;

void func() {

    int b;

    a = 880;
    b = 120;

    printf("In func, a = %d, b = %d\n", a, b);

}

int main()  {

    printf("In main, a = %d, b = %d\n", a, b);   
    func(); 

    return 0;
}
  • extern 关键字,可以i先使用全局变量,然后后面再定义
# include <stdio.h>

void func();

void func() {
    // 先使用extern先使用变量,在后面再定义全局变量
    extern int count;
    count++;
}


// 定义全局变量
int count = 0;

int main() {
    func();

    printf("%d\n", count);

    return 0;

}

定义和声明的区别

  • 当一个变量被定义的时候,编译器为变量申请内存空间并填充一些值。
  • 当一个变量被声明的时候,编译器就知道该变量被定义在其他地方。
  • 声明是通知编译器该变量名及相关类型已经存在,不需要再为此申请内存空间。
  • 局部变量即是声明也是定义。
  • 定义只能来一次,否则就叫重复定义某个同名变量;而声明可以有很多次。

作用域和链接属性

作用域(代码块作用域,文件作用域,原型作用域,函数作用域)
代码块作用域 ({})
# include <stdio.h>

int main(void) { // 写进void 表示没有参数

    int i = 100; // i1

    {
        int i = 110; // i2
        {
            int i = 120; // i3
            printf("i = %d\n", i); // 120
        }
        // 此时i = 110
        {
            printf("i = %d\n", i); // 110
            int i = 130; // i4
            printf("i = %d\n", i); // 130
        }
        printf("i = %d\n", i); // 110
    }
    printf("i = %d\n", i); // 100

    return 0;

}
文件作用域(全局变量和函数名等)
# include <stdio.h>

void func(void);

int main(void) {

    extern int count;
    func();
    count++;
    printf("In main, count = %d\n",count);

    return 0;

}

int count;

void func(void) {
    count++;
    printf("In func, count = %d\n", count);
}
原型作用域(只适用于函数原型中的参数名)
函数作用域(主要用于对goto语句限制在函数里)
连接属性( external(外部的), internal(内部的), none(无) )

(源文件)=编译=》 =链接=》 (可执行文件)

  • external(外部的) :多个文件中声明的同名标识符表示同一个实体

  • internal(内部的) :单个文件中声明的同名标识符表示同一个实体

  • none(无) :声明的同名标识符被当作独立不同的实体

  • 只有具备文件作用域(全局变量或者函数名)的标识符才能拥有external或internal的链接属性,其他作用域的标识符都是none属性。

  • 默认情况下,具备文件作用域的标识符具有external属性。也就是说该标识符允许跨文件访问。对于external属性的标识符,无论在不同文件中声明多少次,表示都是同一个实体。

生存区和存储类型

静态存储区 和 自动存储区
五种不同的存储类型(auto, register, static, extern, typedef)
  • auto (自动变量 ,局部变量,默认的类型)
  • register(寄存器变量,没法通过取址运算符获得地址)
# include <stdio.h>

int main() {
    
    register int i = 520;

    // 报错= error: address of register variable 'i' requested
    // 若想取得 i 的地址,就要去掉 register
    printf("Addr of i = %p\n", &i);

    return 0;
}
  • static(静态局部变量)
# include <stdio.h>

void func(void);
void func(void) {
    static int count = 0; // 作用域并没有改变,只是改变了变量的生存期。

    printf("count = %d\n", count);

    count++;
}

int main(void) {
    int i;
    for(i = 0; i < 10; i++) {
    	/*
			上面的变量带了 static 则结果为 0 1 2 3 4 5 6 7 8 9
			上面的变量没带 static 则结果为 0 0 0 0 0 0 0 0 0 0
		*/
        func();
    }

    return 0;
}
  • extern (告诉编译器,这个函数或者变量在前面已经被定义过了)

  • typedef (用于为数据类型定义一个别名)

递归(recursion)

递归必须有结束条件,否则程序会报错。
# include <stdio.h>

void recursion(void);
void recursion(void) {

    // 做一个计数器作为结束条件
    // 注意用 static 改变变量的生存周期
    static int count = 10;

    printf("Hi! \n");

    if (--count)
    {
        recursion();
    }
    
}

int main(void) {

    recursion();

    return 0;
}
用递归实现阶乘
  • 循环实现
#include <stdio.h>

long fact(int num);
long fact(int num) {

    long result = 1;
    
    for(result; num > 1; num--) {
        result *= num;
    }

    return result;
}


int main() {

    int num;

    printf("请输入一个正整数:");
    scanf("%d", &num);

    printf("%d的阶乘是:%d\n",num, fact(num));

    return 0;
}
  • 递归实现
#include <stdio.h>

long fact(int num);
long fact(int num) {

    long result = 1;
    
    // 递归开始
    if (num > 0)
    {   
        // 真正进行函数调用的地方
        result = num * fact(num - 1);
    } else
    {
        // 递归的结束条件
        result = 1;
    }
    
    return result;
}


int main() {

    int num;

    printf("请输入一个正整数:");
    scanf("%d", &num);

    printf("%d的阶乘是:%d\n",num, fact(num));

    return 0;
}

汉诺塔

#include <stdio.h>

void hanoi(int n, char x, char y, char z);
void hanoi(int n, char x, char y, char z){
	if(n == 1){ // 结束条件,
		//X只剩下一个的时候,将最下面的一个移动到Z上面
		printf("%c --> %c\n", x, z);
	} else {
		// 汉诺塔的递归部分
		hanoi(n - 1, x, z, y); // 借助z将x移动到y上
		printf("%c --> %c\n", x, z);
		hanoi(n - 1, y, x, z); // 借助x将y移动到z上
	}
}

int main() {
	
	int n;
	printf("请输入汉诺塔的层数:");
	
	scanf("%d", &n);
	hanoi(n, 'X', 'Y', 'Z');
	
	return 0;
}

快速排序

# include <stdio.h>

void quick_sort(int array[], int left, int right);
void quick_sort(int array[], int left, int right) {
	int i = left, j = right;
	int temp;
	int pivot; // 基准点
	
    // 选数组中间的元素作为基准点
	pivot = array[(left + right) / 2];
	
	while(i <= j) { // 数组左边开始的下标小于数组右边开始的下标
		
		// 从 左到右 找到大于等于基准点的元素,找到了就先停在这个元素上
		while(array[i] < pivot) {
			i++;
		}
        // 从 右到左 找到小于等于基准点的元素,找到了就先停在这个元素上
		while(array[j] > pivot){
			j--;
		}
        
        // 上面两个元素都找到了
		// 如果i <= j, 则互换
		if(i <= j) {
			temp = array[i];
			array[i] = array[j];
			array[j] = temp;
			i++; // 向左移动一下
			j--; // 向右移动一下
		}
	}

    // 递归部分
    // int i = left, j = right;
	if(left < j){   // 结束条件
            // 左边的数组   0  得到的j  
		quick_sort(array, left, j);
	}
	if(i < right){  // 结束条件
            // 又边的数组 得到的i  右边数组的长度  
		quick_sort(array, i, right);
	}
}

int main() {
	int array[] = {73, 108, 118, 101, 70, 105, 115, 104, 67, 46, 99, 111, 109};
	
	int i, length;
	
    // 求数组长度
	length = sizeof(array) / sizeof(array[0]);
	
	quick_sort(array, 0, length-1);
	
	printf("排序后的结果是:");
	
	for(i=0;i<length;i++){
		printf(" %d ", array[i]);
	}

	putchar('\n');
	return 0;
}

动态内存管理1

  • malloc
    • 申请动态内存空间(存放在堆上)
    • 函数原型:
      void *malloc(size_t size)
  • free
    • 释放动态内存空间
    • 函数原型:
      void free(void *ptr)
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr;

    // 在堆里面申请一个内存空间,把地址给指针
    ptr = (int *)malloc(sizeof(int));
    if (ptr == NULL)
    {
        printf("内存分配失败!\n");
        exit(1);
    }
    
    printf("请输入一个整数:");
    scanf("%d", ptr);

    printf("你输入的整数是:%d\n", *ptr);

    // 释放内存空间
    free(ptr);

    // printf("你输入的整数是:%d\n", *ptr); // 错误,释放以后就不存在了
    return 0;
}
  • calloc
    • 申请并初始化一系列内存空间
  • realloc
    • 重新分配内存空间

动态内存管理2

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

int main() {

    int *ptr = NULL;
    int num,i;

    printf("请输入带录入整数的个数:");
    scanf("%d", &num);

    ptr = (int *)malloc(num * sizeof(int));

    for (i = 0; i < num; i++)
    {
        printf("请输入第%d个整数:", i + 1);
        scanf("%d", &ptr[i]);
    }

    printf("你录入的整数是:");
    for(i = 0; i < num; i++) {
        printf(" %d ", ptr[i]);
    }
    putchar('\n');

    // 释放内存
    free(ptr);

    

    return 0;
}
  • 以mem开头的函数被编入字符串标准库,函数的声明包含在string.h这个头文件中:
    • memset ==》 使用一个常量字节填充内存空间
    • memcpy ==》 拷贝内存空间
    • memmove ==》 拷贝内存空间
    • memcmp ==》 比较内存空间
    • memchr ==》 在内存空间中搜索一个字符
# include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define N 10

int main() {

   int *ptr = NULL;
   int i;

   ptr = (int *)malloc(N * sizeof(int));
   if (ptr == NULL) // 表示malloc函数调用失败
   {
       exit(1); // 退出程序
   }

   // 要初始化的内存块,指定要初始化的常量,内存块的连续尺寸
   memset(ptr, 0, N * sizeof(int));
   
    for(i = 0; i < N; i++) {
        printf(" %d ", ptr[i]);
    }
    putchar('\n');
   
    free(ptr);
    return 0;
}
calloc
  • 函数原型

    • void *calloc(size_t nmemb, size_t size);
  • calloc函数在内存中动态地申请nmemb个长度为size的连续内存空间(即申请的总空间尺寸为nmemb * size),这些内存空间全部被初始化为 0。

  • calloc函数与malloc函数的一个重要区别是:

    • calloc函数在申请完内存后,自动初始化该内存空间为零
    • malloc函数不进行初始化操作,这里边的数据是随机的
  • 两种等价的写法

// calloc() 分配内存空间并初始化
int *ptr = (int *)calloc(8, sizeof(int));

// malloc()分配内存空间并用 memset() 初始化
int *ptr = (int *)malloc(8 * sizeof(int));
memset(ptr, 0, 8 * sizeof(int));
realloc
  • 函数原型:
    • void *realloc(void *ptr, size_t size);
  • 已经下几点是需要注意的:
    • realloc函数修改ptr指向的内存空间大小为size字节
    • 如果新分配的内存空间比原来的大,则旧内存块的数据不会发生改变;如果显得内存空间大小小于旧的内存空间,可能导致数据丢失,慎用!
    • 该函数将移动内存空间的数据并返回新的指针
# include <stdio.h>
# include <stdlib.h>

int main() {

    int i, num;
    int count = 0;
    int *ptr = NULL; // 必须初始化为NULL

    do{
        printf("请输入一个整数(输入 -1 表示结束):");
        scanf("%d", &num);
        count++;

        ptr = (int *)realloc(ptr, count * sizeof(int));
        if (ptr == NULL)
        {
            exit(1);
        }

        ptr[count - 1] = num;
        
    }while(num != -1);

    printf("输入的整数分别是:");
    for (i = 0; i < count; i++)
    {
        printf(" %d ", ptr[i]);
    }
    putchar('\n');

    free(ptr);
    
    return 0;
}

C语言的内存布局

堆:

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩展或缩小。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上;当利用free等函数释放内存时,被释放的内存从堆中被删除。

栈:

平时听到的堆栈,一般指的就是这个栈。栈是函数执行的内存区域,通常和堆共享同一片区域 。

堆和栈的区别
  • 申请方式:

    • 堆由程序员手动申请
    • 栈由系统自动分配
  • 释放方式:

    • 堆由程序员手动释放
    • 栈由系统自动释放
  • 生存周期:

    • 堆的生存周期由动态申请到程序员主动释放为止,不同函数之间均可自由访问。
    • 栈的生存周期由函数用开始到函数返回时结束,函数之间的局部变量不能相互访问
  • 发展方向:

    • 堆和其它区段一样,都是从低地址向高地址发展
    • 栈则相反,是由高地址向低地址发展

宏定义的高级用法

C语言三大预处理功能
  • 文件包含

  • 条件编译

  • 宏定义的作用:替换

  • 不带参数的宏定义

    define PI 3.14

  • 可以用 # undef 来终止宏定义的作用域

  • 宏定义允许嵌套

  • 名字用大写表示

# include <stdio.h>

# define R 6371
# define PI 3.14 // 后面不能加分号
// 宏定义的嵌套,在一个宏定义使用另外一个宏定义
# define V PI * R * R * R * 4 / 3

int main() {
    int r;
    float s;

    printf("请输入圆的半径:");
    scanf("%d", &r);
//# undef PI  // 结束宏定义PI的作用范围
    s = PI * r * r;
    printf("圆的面积是:%.2f\n", s);

    printf("地球的体积大概是:%.2f\n", V);
    return 0;
}
带参数的宏定义
  • #define MAX(x, y)(((x) > (y)) ? (x) : (y)) // 名字和参数的括号之间不能有空格
# include <stdio.h>

#define MAX(x, y)(((x) > (y)) ? (x) : (y))

int main() {

    int x,y;
    printf("请输入两个整数:");
    scanf("%d%d", &x, &y);
    printf("%d 是较大的那个数!\n",MAX(x, y));

    return 0;
}
# include <stdio.h>

# define SQUARE(x) ((x) * (x))

int main() {

    int i = 1;
    
    while(i <= 100) {
        printf("%d 的平方是%d\n", i,SQUARE(i));
        i++;
    }

    return 0;
}

内联函数和一些鲜为人知的技巧

内联函数来解决程序中函数调用的效率问题
  • 加上 inline 变成内联函数
# include <stdio.h>

//加上inline关键字变成内联函数
inline int square(int x);
inline int square(int x){
    return x * x;
}

int main() {

    int i = 1;
    
    while(i <= 100) {
        printf("%d 的平方是%d\n", i - 1,square(i++));
    }

    return 0;
}
  • 和 ## 是两个预处理运算符

# include <stdio.h>

// # 会把后面的s(参数)变成字符串的形式
# define STR(s) # s

int main() {

    printf("%s\n", STR(FISHC));

    return 0;
}
  • 记号链接运算符

# include <stdio.h>

// 将两个 字符连接起来,类似字符串的链接
# define TOGETHER(x, y) x ## y

int main() {

    printf("%d\n", TOGETHER(5, 20)); // 520

    return 0;
}
可变参数
  • #define SHOWLIST(…) printf(#VA_ARGS)
  • 其中 … 表示使用可变参数,__VA_ARGS__在预处理中被实际的参数所替换
# include <stdio.h>

// 其中 ... 表示使用可变参数,__VA_ARGS__在预处理中被实际的参数所替换
#define SHOWLIST(...) printf(#__VA_ARGS__) // 一个 # 转换成字符串

int main() {

    SHOWLIST(FishC, 520, 3.14\n); // FishC, 520, 3.14
    return 0;
}
# include <stdio.h>

# define PRINT(format, ...) printf(# format, ##__VA_ARGS__)

int main() {

    PRINT(num = %d\n, 520); // num = 520
    PRINT(Hello FishC!\n); // Hello FishC!

    return 0;
}

结构体

结构体声明
struct 结构体名称
{
	结构体成员1;
	结构体成员2;
	结构体成员3;
	......
};

// 生成结构体

// 法一:局部变量
struct  结构体名称 结构体变量名 

// 法二:全局变量
struct 结构体名称
{
	结构体成员1;
	结构体成员2;
	结构体成员3;
	......
}结构体变量名;
# include <stdio.h>

// 结构体
struct Book
{
    char title[128];
    char author[40];
    float price;
    unsigned int date;
    char publisher[40];
}book2; // 方法二直接在结构体后面定义
// 此时 book2 是全局变量


int main(void) {
   // struct  结构体名称 结构体变量名
   // 方法一
    struct Book book1;

    printf("请输入书名:");
    scanf("%s", book2.title);
    printf("请输入作者:");
    scanf("%s", book2.author);
    printf("请输入售价:");
    // 除字符串以外都要输入 &(取址运算符) 
    scanf("%f", &book2.price);  // float类型,用 %f 来接受
    printf("请输入出版日期:");
    scanf("%d", &book2.date);
    printf("请输入出版社:");
    scanf("%s", book2.publisher);

    printf("\n=====数据录入完毕=====\n");

    printf("书名:%s\n", book2.title);
    printf("作者:%s\n", book2.author);
    printf("售价:%.2f\n", book2.price);
    printf("日期:%u\n", book2.date); // 无符号整形用 %u
    printf("出版社:%s\n", book2.publisher);

    return 0;
}
初始化结构体变量
// 全部初始化
struct Book book = {
	"《带你学C带你飞》",
	"小甲鱼",
	48.8,
	20171111,
	"清华大学出版社"
}

// 只初始化某个变量,可以按照成员顺序初始化
struct Book book {
	.publisher = "清华大学出版社", //不同属性用 , 隔开
	.price = 48.8,
	.date = 20171111 // 最后一个变量不用加 ,
}
内存对齐
系统为了方便读取, 调整变量的内存空间,全都调整到最大的内存空间去表示。
  • 例如:char int char 。两个char本来是一个内存空间,可是内存会经过调整调成四个(都是用一个剩下三个),也就是一共用 12 个空间
  • 例如:char char int 。两个 char 就会直接公用一个int的空间(用两个剩下两个),也就是一共 8 个空间。
# include <stdio.h>

int main() {
    struct A {
        char a;
        int b;
        char c;
    } a = {'x', 520, '0'};

    struct B {
        char a;
        char c;
        int b;
    } b = {'x', '0', 520};
   
    printf("size of a = %d\n", sizeof(a)); // 12
    printf("size of b = %d\n", sizeof(b)); // 8
    return 0;
}
嵌套数组
# include <stdio.h>
// 结构体的嵌套

struct Date {
    int year;
    int month;
    int day;
};


// 结构体
struct Book
{
    char title[128];
    char author[40];
    float price;
    // 结构体的嵌套,在一个结构体里面套用另外一个结构体
    struct Date date;
    char publisher[40];
}book = {
    "《带你学C带你飞》",
    "小甲鱼",
    48.8,
    {2017, 11, 11},
    "清华大学出版社"
}; 


int main(void) {

    printf("书名:%s\n", book.title);
    printf("作者:%s\n", book.author);
    printf("售价:%.2f\n", book.price);
    printf("日期:%d.%d.%d\n", book.date.year, book.date.month, book.date.day);
    printf("出版社:%s\n", book.publisher);

    return 0;
}

结构体数组和结构体指针

结构体数组
  • 定义方法一:在声明结构体的时候进行定义
struct 结构体名称 {
	结构体成员;
} 数组名[长度];
  • 方法二:先声明一个结构体类型(比如上面的Book),再用此类型定义一个结构体数组:
struct 结构体名称 {
	结构体成员;
};
struct 结构体名称 数组名[长度];
结构体指针
// 结构体的变量名不是指向结构体的
struct Book *pt; 
pt = &book; // 使用取址运算符取到地址然后再赋值
 // 指向结构体Book的指针pt
  • 通过结构体指针访问结构体的成员有两种方法(.用于对象; ->用于指针):
  • 法一:(*结构体指针).成员名
# include <stdio.h>
// 结构体的嵌套

struct Date {
    int year;
    int month;
    int day;
};


// 结构体
struct Book
{
    char title[128];
    char author[40];
    float price;
    // 结构体的嵌套,在一个结构体里面套用另外一个结构体
    struct Date date;
    char publisher[40];
}book = {
    "《带你学C带你飞》",
    "小甲鱼",
    48.8,
    {2017, 11, 11},
    "清华大学出版社"
}; 


int main(void) {

    struct Book *pt;
    pt = &book;

    printf("书名:%s\n", (*pt).title);
    printf("作者:%s\n", (*pt).author);
    printf("售价:%.2f\n", (*pt).price);
    printf("日期:%d-%d-%d\n", (*pt).date.year, (*pt).date.month, (*pt).date.day);
    printf("出版社:%s\n", (*pt).publisher);

    return 0;
}
  • 法二:结构体指针->成员名

include <stdio.h>

// 结构体的嵌套

struct Date {
int year;
int month;
int day;
};

// 结构体
struct Book
{
char title[128];
char author[40];
float price;
// 结构体的嵌套,在一个结构体里面套用另外一个结构体
struct Date date;
char publisher[40];
}book = {
“《带你学C带你飞》”,
“小甲鱼”,
48.8,
{2017, 11, 11},
“清华大学出版社”
};

int main(void) {

struct Book *pt;
pt = &book;

printf("书名:%s\n", pt->title);
printf("作者:%s\n", pt->author);
printf("售价:%.2f\n", pt->price);
printf("日期:%d-%d-%d\n", pt->date.year, pt->date.month, pt->date.day);
printf("出版社:%s\n", pt->publisher);

return 0;

}

传递结构体变量和结构体指针

传递结构体变量(结构体太大,效率低)
# include <stdio.h>

int main() {

    struct Test
    {
        int x;
        int y;
    }t1, t2; // 定义两个变量

    t1.x = 3;
    t1.y = 4;

    t2 = t1; // 两个结构体之间是可以直接赋值的
    
    printf("t2.x = %d, t2.y = %d\n", t2.x, t2.y);

    return 0;
}
# include <stdio.h>

struct Date {
    int year;
    int month;
    int day;
};

struct  Book
{
    char title[128];
    char author[40];
    float price;
    struct Date date;
    char publisher[40];
};

struct Book getInput(struct Book book);
void printBook(struct Book book);

struct Book getInput(struct Book book) {

    printf("请输入书名:");
    scanf("%s", book.title);
    printf("请输入作者:");
    scanf("%s", book.author);
    printf("请输入售价:");
    // 除字符串以外都要输入 &(取址运算符) 
    scanf("%f", &book.price);  // float类型,用 %f 来接受
    printf("请输入出版日期:");
    scanf("%d-%d-%d", &book.date.year, &book.date.month, &book.date.day);
    printf("请输入出版社:");
    scanf("%s", book.publisher);

    return book;

}

void printBook(struct Book book) {
    printf("书名:%s\n", book.title);
    printf("作者:%s\n", book.author);
    printf("售价:%.2f\n", book.price);
    printf("日期:%d-%d-%d\n", book.date.year, book.date.month, book.date.day);
    printf("出版社:%s\n", book.publisher);
}

int main(void) {

    struct Book b1,b2;

    printf("请录入第一本数的信息...\n");
    b1 = getInput(b1);

    putchar('\n');

    printf("请录入第二本数的信息...\n");
    b2 = getInput(b2);

    printf("\n\n录入完毕,现在开始打印验证...\n\n");

    printf("打印第一本书的信息...\n");
    printBook(b1);

    putchar('\n');

    printf("打印第二本书的信息...\n");
    printBook(b2);

    return 0;
}
传递结构体指针(效率高)
# include <stdio.h>

struct Date {
    int year;
    int month;
    int day;
};

struct  Book
{
    char title[128];
    char author[40];
    float price;
    struct Date date;
    char publisher[40];
};

void getInput(struct Book *book);
void printBook(struct Book *book);

void getInput(struct Book *book) { // 指针不需要有返回值

    printf("请输入书名:");
    scanf("%s", book->title);
    printf("请输入作者:");
    scanf("%s", book->author);
    printf("请输入售价:");
    scanf("%f", &book->price);  // float类型,用 %f 来接受
    printf("请输入出版日期:");
    scanf("%d-%d-%d", &book->date.year, &book->date.month, &book->date.day);
    printf("请输入出版社:");
    scanf("%s", book->publisher);

}

void printBook(struct Book *book) {
    printf("书名:%s\n", book->title);
    printf("作者:%s\n", book->author);
    printf("售价:%.2f\n", book->price);
    printf("日期:%d-%d-%d\n", book->date.year, book->date.month, book->date.day);
    printf("出版社:%s\n", book->publisher);
}

int main(void) {

    struct Book b1,b2;

    printf("请录入第一本数的信息...\n");
    getInput(&b1); // 指针类型传入地址值

    putchar('\n');

    printf("请录入第二本数的信息...\n");
    getInput(&b2);

    printf("\n\n录入完毕,现在开始打印验证...\n\n");

    printf("打印第一本书的信息...\n");
    printBook(&b1);

    putchar('\n');

    printf("打印第二本书的信息...\n");
    printBook(&b2);

    return 0;
}
使用malloc函数为结构体分配存储空间
# include <stdio.h>
# include <stdlib.h>

struct Date {
    int year;
    int month;
    int day;
};

struct  Book
{
    char title[128];
    char author[40];
    float price;
    struct Date date;
    char publisher[40];
};

void getInput(struct Book *book);
void printBook(struct Book *book);

void getInput(struct Book *book) { // 指针不需要有返回值

    printf("请输入书名:");
    scanf("%s", book->title);
    printf("请输入作者:");
    scanf("%s", book->author);
    printf("请输入售价:");
    scanf("%f", &book->price);  // float类型,用 %f 来接受
    printf("请输入出版日期:");
    scanf("%d-%d-%d", &book->date.year, &book->date.month, &book->date.day);
    printf("请输入出版社:");
    scanf("%s", book->publisher);

}

void printBook(struct Book *book) {
    printf("书名:%s\n", book->title);
    printf("作者:%s\n", book->author);
    printf("售价:%.2f\n", book->price);
    printf("日期:%d-%d-%d\n", book->date.year, book->date.month, book->date.day);
    printf("出版社:%s\n", book->publisher);
}

int main(void) {

    struct Book *b1, *b2; // 定义结构体指针

    // malloc 申请指针空间
    // 通过 malloc 申请一个 结构体Book 类型的指针,大小为 struct Book 结构体的大小,赋值给 b1 变量
    b1 = (struct Book *)malloc(sizeof(struct Book));
    b2 = (struct Book *)malloc(sizeof(struct Book));

    if (b1 == NULL || b2 == NULL) { // 判断指针是否申请成功,若不成功直接退出
       printf("内存分配失败!\n");
       // 调用 exit(1) 需要引用 # include <stdlib.h> 头文件
       exit(1); 
    }

    printf("请录入第一本数的信息...\n");
    getInput(b1); // 指针类型传入地址值

    putchar('\n');

    printf("请录入第二本数的信息...\n");
    getInput(b2);

    printf("\n\n录入完毕,现在开始打印验证...\n\n");

    printf("打印第一本书的信息...\n");
    printBook(b1);

    putchar('\n');

    printf("打印第二本书的信息...\n");
    printBook(b2);

    // 释放空间,与malloc相互对应
    free(b1);
    free(b2);

    return 0;
}

单链表

# include <stdio.h>

int main() {
	
	struct Test {
		int x;
		int y;
		struct Test *test;
	};
	
	return 0;	
	
}

  • 链表包括信息域和指针域
图书馆信息存入(头插法)
# include <stdio.h>
# include <stdlib.h>

struct Book {
	// 信息域
	char title[128];
	char author[40];
	// 指针域
	struct Book *next;
};

// 声明函数
void getInput(struct Book *book);
void addBook(struct Book **library);
void printLibrary(struct Book *library);
void releaseLibrary(struct Book **library);

void getInput(struct Book *book) {
    printf("请输入书名:");
    scanf("%s", book->title);
    printf("请输入作者:");
    scanf("%s",book->author);
}


// library 代表头指针,里面的值是指针,要改变头指针的值,所以要用 ** 来解引用
void addBook(struct Book **library) { // 双重解引用,指向指针的指针
    struct Book *book, *temp;
    book = (struct Book *)malloc(sizeof(struct Book));
    if(book == NULL) {
        printf("内存分配失败了! \n");
        exit(1);
    }
    getInput(book); // 传入指针book

    if(*library != NULL) {

        /* 头插法 */

        temp = *library;
        *library = book;
        book->next = temp;
    } else { // 如果为 NULL 则代表是空链表
        *library = book;
        book->next = NULL;
    }
}

void printLibrary(struct Book *library) {
    struct Book *book;
    int count = 1;
    book = library;
    while(book != NULL){
        printf("Book: %d \n", count);
        printf("书名:%s \n", book->title);
        printf("作者: %s \n", book->author);

        book = book->next;
        count++;
    }
}
void releaseLibrary(struct Book **library) {
    struct Book *temp;
    while(*library != NULL) {
        temp = *library;
        *library = (*library)->next;
        free(temp);
    }
}

int main() {

    // 作为头指针,初始值为NULL
    struct Book *library = NULL;
    int ch;

    // 录入细信息
    while(1) {
        printf("请问是否需要录入书籍信息(Y/N):");
        do{
            ch = getchar();
        } while (ch != 'Y' && ch != 'N');

        if(ch == 'Y') {
            // 传入一个地址,不能直接传入library
            addBook(&library);
        } else {
            break;
        }
    }

    printf("请问是否需要打印图书信息(Y/N):");
    do {
        ch = getchar();
    } while (ch != 'Y' && ch != 'N');

    if(ch == 'Y') {
        printLibrary(library);
    }

    // 不需要以后直接释放掉
    releaseLibrary(&library);

	return 0;
}
图书信息存入(尾插法)
# include <stdio.h>
# include <stdlib.h>

struct Book {
    // 信息域
    char title[128];
    char author[40];
    // 指针域
    struct Book *next;
};

// 声明函数
void getInput(struct Book *book);
void addBook(struct Book **library);
void printLibrary(struct Book *library);
void releaseLibrary(struct Book **library);

void getInput(struct Book *book) {
    printf("请输入书名:");
    scanf("%s", book->title);
    printf("请输入作者:");
    scanf("%s",book->author);
}


// library 代表头指针,里面的值是指针,要改变头指针的值,所以要用 ** 来解引用
void addBook(struct Book **library) { // 双重解引用,指向指针的指针
    struct Book *book, *temp;
    book = (struct Book *)malloc(sizeof(struct Book));
    if(book == NULL) {
        printf("内存分配失败了! \n");
        exit(1);
    }
    getInput(book); // 传入指针book

    if(*library != NULL) {

        /* 尾插法 */
        temp = *library;

        //定义单链表的尾部位置
        while(temp->next != NULL) { // 遍历找到单链表的尾部位置
            temp = temp->next;
        }

        // 插入数据
        temp->next = book;
        book->next = NULL;

    } else { // 如果为 NULL 则代表是空链表
        *library = book;
        book->next = NULL;
    }
}

void printLibrary(struct Book *library) {
    struct Book *book;
    int count = 1;
    book = library;
    while(book != NULL){
        printf("Book: %d \n", count);
        printf("书名:%s \n", book->title);
        printf("作者: %s \n", book->author);

        book = book->next;
        count++;
    }
}
void releaseLibrary(struct Book **library) {
    struct Book *temp;
    while(*library != NULL) {
        temp = *library;
        *library = (*library)->next;
        free(temp);
    }
}

int main() {

    // 作为头指针,初始值为NULL
    struct Book *library = NULL;
    int ch;

    // 录入细信息
    while(1) {
        printf("请问是否需要录入书籍信息(Y/N):");
        do{
            ch = getchar();
        } while (ch != 'Y' && ch != 'N');

        if(ch == 'Y') {
            // 传入一个地址,不能直接传入library
            addBook(&library);
        } else {
            break;
        }
    }

    printf("请问是否需要打印图书信息(Y/N):");
    do {
        ch = getchar();
    } while (ch != 'Y' && ch != 'N');

    if(ch == 'Y') {
        printLibrary(library);
    }

    // 不需要以后直接释放掉
    releaseLibrary(&library);

    return 0;
}
图书信息存入(尾插法(优化))
  • 添加tail始终指向单链表的尾部节点
# include <stdio.h>
# include <stdlib.h>

struct Book {
    // 信息域
    char title[128];
    char author[40];
    // 指针域
    struct Book *next;
};

// 声明函数
void getInput(struct Book *book);
void addBook(struct Book **library);
void printLibrary(struct Book *library);
void releaseLibrary(struct Book **library);

void getInput(struct Book *book) {
    printf("请输入书名:");
    scanf("%s", book->title);
    printf("请输入作者:");
    scanf("%s",book->author);
}


// library 代表头指针,里面的值是指针,要改变头指针的值,所以要用 ** 来解引用
void addBook(struct Book **library) { // 双重解引用,指向指针的指针
    struct Book *book;

    /* 静态变量,保存单链表尾部的位置 */
    
    static struct Book *tail;

    book = (struct Book *)malloc(sizeof(struct Book));
    if(book == NULL) {
        printf("内存分配失败了! \n");
        exit(1);
    }
    getInput(book); // 传入指针book

    if(*library != NULL) {

        /* 尾插法(优化) */

        tail->next = book;
        book->next = NULL;

    } else { // 如果为 NULL 则代表是空链表
        *library = book;
        book->next = NULL;
    }

    tail = book;
}

void printLibrary(struct Book *library) {
    struct Book *book;
    int count = 1;
    book = library;
    while(book != NULL){
        printf("Book: %d \n", count);
        printf("书名:%s \n", book->title);
        printf("作者: %s \n", book->author);

        book = book->next;
        count++;
    }
}
void releaseLibrary(struct Book **library) {
    struct Book *temp;
    while(*library != NULL) {
        temp = *library;
        *library = (*library)->next;
        free(temp);
    }
}

int main() {

    // 作为头指针,初始值为NULL
    struct Book *library = NULL;
    int ch;

    // 录入细信息
    while(1) {
        printf("请问是否需要录入书籍信息(Y/N):");
        do{
            ch = getchar();
        } while (ch != 'Y' && ch != 'N');

        if(ch == 'Y') {
            // 传入一个地址,不能直接传入library
            addBook(&library);
        } else {
            break;
        }
    }

    printf("请问是否需要打印图书信息(Y/N):");
    do {
        ch = getchar();
    } while (ch != 'Y' && ch != 'N');

    if(ch == 'Y') {
        printLibrary(library);
    }

    // 不需要以后直接释放掉
    releaseLibrary(&library);

    return 0;
}
按照书名和作者名搜索图书
# include <stdio.h>
# include <stdlib.h>
# include <string.h>

struct Book {
    // 信息域
    char title[128];
    char author[40];
    // 指针域
    struct Book *next;
};

// 声明函数
void getInput(struct Book *book);
void addBook(struct Book **library);
void printLibrary(struct Book *library);
struct Book *searchBook(struct Book *library, char *target);
void printBook(struct Book *book);
void releaseLibrary(struct Book **library);

void getInput(struct Book *book) {
    printf("请输入书名:");
    scanf("%s", book->title);
    printf("请输入作者:");
    scanf("%s",book->author);
}


// library 代表头指针,里面的值是指针,要改变头指针的值,所以要用 ** 来解引用
void addBook(struct Book **library) { // 双重解引用,指向指针的指针
    struct Book *book;

    /* 静态变量,保存单链表尾部的位置 */

    static struct Book *tail;

    book = (struct Book *)malloc(sizeof(struct Book));
    if(book == NULL) {
        printf("内存分配失败了! \n");
        exit(1);
    }
    getInput(book); // 传入指针book

    if(*library != NULL) {

        /* 尾插法(优化) */

        tail->next = book;
        book->next = NULL;

    } else { // 如果为 NULL 则代表是空链表
        *library = book;
        book->next = NULL;
    }

    tail = book;
}

void printLibrary(struct Book *library) {
    struct Book *book;
    int count = 1;
    book = library;
    while(book != NULL){
        printf("Book: %d \n", count);
        printf("书名:%s \n", book->title);
        printf("作者: %s \n", book->author);

        book = book->next;
        count++;
    }
}

// 通过书名或者作者,搜索图书部分的函数
struct Book *searchBook(struct Book *library, char *target) {
    struct Book *book;

    book = library;
    while(book != NULL) {
        // strcmp 需要
        if(!strcmp(book->title, target) || !strcmp(book->author, target)) {
            break;
        }
        book = book->next;
    }
    return book;
}
// 找到以后打印图书
void printBook(struct Book *book) {
    printf("书名:%s", book->title);
    printf("作者:%s", book->author);
}

void releaseLibrary(struct Book **library) {
    struct Book *temp;
    while(*library != NULL) {
        temp = *library;
        *library = (*library)->next;
        free(temp);
    }
}

int main() {

    // 作为头指针,初始值为NULL
    struct Book *library = NULL;
    struct Book *book;
    char input[128];
    int ch;

    // 录入细信息
    while(1) {
        printf("请问是否需要录入书籍信息(Y/N):");
        do{
            ch = getchar();
        } while (ch != 'Y' && ch != 'N');

        if(ch == 'Y') {
            // 传入一个地址,不能直接传入library
            addBook(&library);
        } else {
            break;
        }
    }

    printf("请问是否需要打印图书信息(Y/N):");
    do {
        ch = getchar();
    } while (ch != 'Y' && ch != 'N');

    if(ch == 'Y') {
        printLibrary(library);
    }

    printf("\n请输入书名或作者:\n");
    scanf("%s", input);

    book = searchBook(library,input);
    if(book == NULL) {
        printf("很抱歉,没能找到!\n");
    } else {
        do {
            printf("已找到符合条件的图书...\n");
            printBook(book);
        } while((book = searchBook(book->next, input)) != NULL);
    }


    // 不需要以后直接释放掉
    releaseLibrary(&library);

    return 0;
}
单链表的插入
# include <stdio.h>
# include <stdlib.h>

struct Node {
    int value;
    struct Node *next;
};

void insertNode(struct Node **head, int value);
void printNode(struct Node *head);

// 传入值指向Node结构体指针的指针,所以传 **head
void insertNode(struct Node **head, int value) {

    // previous --> new --> current

    // 记录要插入节点的上一个节点
    struct Node *previous;
    // 表示当前的指针,指向比value大的节点的指针
    // 记录要插入节点的下一个节点
    struct Node *current;
    // 要插入的新节点 
    struct Node *new;

    current = *head;
    previous = NULL;

    // current != NULL 是空链表的情况
    // current->value < value 找到要拆入的节点的位置就停止循环
    while(current != NULL && current->value < value) {
        previous = current;
        current = current->next;
    }

    new = (struct Node *)malloc(sizeof(struct Node));
    if (new == NULL)
    {
        printf("内存分配失败!");
        exit(1);
    }
    
    new->value = value;
    new->next = current;

    if(previous == NULL) { // 传入的是空链表
        //直接吧new节点放在第一个
        *head = new;
    } else {
        previous->next = new;
    }

}

void printNode(struct Node *head) {
    struct Node *current;

    current = head;
    while(current != NULL) {
        printf(" %d ", current->value);
        current = current->next;
    }
    putchar('\n');
}

int main() {

    struct Node *head = NULL; //定义一个头指针
    int input;

    while(1) {
        printf("请输入一个整数(输入-1表示结束):");
        scanf("%d", &input);
        if(input == -1) {
            printf("程序结束。");
            break;
        }
        insertNode(&head, input);
        printNode(head);
    }

    return 0;
}
单链表的删除
# include <stdio.h>
# include <stdlib.h>

struct Node {
    int value;
    struct Node *next;
};

void insertNode(struct Node **head, int value);
void printNode(struct Node *head);


// 传入值指向Node结构体指针的指针,所以传 **head
void insertNode(struct Node **head, int value) {

    // previous --> new --> current

    // 记录要插入节点的上一个节点
    struct Node *previous;
    // 表示当前的指针,指向比value大的节点的指针
    // 记录要插入节点的下一个节点
    struct Node *current;
    // 要插入的新节点 
    struct Node *new;

    current = *head;
    previous = NULL;

    // current != NULL 是空链表的情况
    // current->value < value 找到要拆入的节点的位置就停止循环
    while(current != NULL && current->value < value) {
        previous = current;
        current = current->next;
    }

    new = (struct Node *)malloc(sizeof(struct Node));
    if (new == NULL)
    {
        printf("内存分配失败!");
        exit(1);
    }
    
    new->value = value;
    new->next = current;

    if(previous == NULL) { // 传入的是空链表
        //直接吧new节点放在第一个
        *head = new;
    } else {
        previous->next = new;
    }

}

void printNode(struct Node *head) {
    struct Node *current;

    current = head;
    while(current != NULL) {
        printf(" %d ", current->value);
        current = current->next;
    }
    putchar('\n');
}

void deleteNode(struct Node **head, int value) {
    struct Node *previous;
    struct Node *current;

    current = *head;
    previous = NULL;

    while(current != NULL && current->value != value) {
        previous = current;
        current = current->next;
    }

    if(current == NULL) {
        printf("找不到匹配的节点!\n");
        return ;
    } else {
        if(previous == NULL) {
            *head = current->next;
        } else {
            previous->next = current->next;
        }
        free(current);
    }
    
}

int main() {

    struct Node *head = NULL; //定义一个头指针
    int input;

    printf("开始测试插入整数:...\n");
    while(1) {
        printf("请输入一个整数(输入-1表示结束):");
        scanf("%d", &input);
        if(input == -1) {
            printf("程序结束。");
            break;
        }
        insertNode(&head, input);
        printNode(head);
    }


    printf("开始测试删除整数:...\n");
    while(1) {
        printf("请输入一个整数(输入-1表示结束):");
        scanf("%d", &input);
        if(input == -1) {
            break;
        }
        deleteNode(&head, input);
        printNode(head);
    }

    return 0;
}

内存池

  • 内存池:就是让程序额外维护一个缓存区域
  • 使用单链表为维护一个简单的内存池
  • 只需要将没有用的内存空间地址一次用一个单链表记录下来;再次需要的时候,从这个单链表中获取即可。
# include <stdio.h>
# include <stdlib.h>
# include <string.h>

# define MAX 1024

struct Person {
    char name[40];
    char phone[20];
    struct Person *next;
};

// 定义一个把内存池(单链表);
struct Person *pool = NULL;
int count;

// 得到用户输入的数据
void getInput(struct Person *person);
// 打印出通讯录中的人员和信息
void printPerson(struct Person *person);
// 添加用户
void addPerson(struct Person **contacts);
// 修改用户的电话号码
void changePerson(struct Person *contacts);
// 删除用户的信息
void delPerson(struct Person **contacts);
// 查找用户,返回一个指向Person结构体的指针
struct Person *findPerson(struct Person *contacts);
// 显示整个通讯录的信息
void displayContacts(struct Person *contacts);
// 释放通讯录
void releaseContacts(struct Person **contacts);
// 释放内存池
void releasePool();

void getInput(struct Person *person) {
    printf("请输入姓名:");
    scanf("%s", person->name);
    printf("请输入电话:");
    scanf("%s", person->phone);
}

void printPerson(struct Person *person) {
    printf("联系人:%s\n", person->name);
    printf("电话:%s\n", person->phone);
}

void addPerson(struct Person **contacts) {
    struct Person *person;
    struct Person *temp;

    // 如果内存池非空,则之家在里面获取空间
    if(pool != NULL) {
        person = pool;
        pool = pool->next;
        count--;
    } else {
        // 如果内存池为空,则调用malloc函数申请新的内存空间
        person = (struct Person *)malloc(sizeof(struct Person));
        if(person == NULL) {
            printf("内存分配失败!\n");
            exit(1);
        }
    }

    getInput(person);

    // 将person用头插法添加到通讯录中
    if(*contacts != NULL) {
        temp = *contacts;
        *contacts = person;
        person->next = temp;
    } else {
        *contacts = person;
        person->next = NULL;
    }

}

void changePerson(struct Person *contacts) {
    struct Person *person;

    person = findPerson(contacts);
    if(person == NULL) {
        printf("找不到该联系人!\n");
    } else {
        printf("请输入新的联系电话:");
        scanf("%s", person->phone);
    }
}

void delPerson(struct Person **contacts) {
    struct Person *temp;
    struct Person *person;
    struct Person *current;
    struct Person *previous;

    //先找到要删除的节点指针
    person = findPerson(*contacts);
    if(person == NULL) {
        printf("找不到该联系人!\n");
    } else {
        current = *contacts;
        previous = NULL;

        // current定位到待删除的节点
        while(current != NULL && current != person) {
            previous = current;
            current = current->next;
        }
        if(previous == NULL) {
            // 待删除的节点是第一个节点
            *contacts = current->next;
        } else {
            // 待删除的节点不是第一个节点
            previous->next = current->next;
        }

        // 先判断内存池是不是有空位
        if(count < MAX) {
            // 头插法
            if(pool != NULL) { // pool不是第一个节点
                temp = pool;
                pool = person;
                person->next = temp;
            } else {
                pool = person;
                person->next = NULL;
            }
            count++;
        } else {
            free(person);
        }
        printf("该联系人已删除。");
    }
}

struct Person *findPerson(struct Person *contacts) {
    struct Person *current;
    char input[40];

    printf("请输入联系人:");
    scanf("%s", input);

    current = contacts;
    while (current != NULL && strcmp(current->name, input)) {
        current = current->next;
    }

    return current;
}

void displayContacts(struct Person *contacts) {
    struct Person *current;
    int flg = 1;

    current = contacts;
    // 传近来的通讯录里面有人
    while(current != NULL) {
        flg = 0;

        printPerson(current);
        current = current->next;
    }
    if(current == NULL && flg) {
        printf("通讯录为空,我未找到任何联系人!");
    }
}

// 释放通讯录
void releaseContacts(struct Person **contacts) {
    struct Person *temp;

    while(*contacts != NULL) {
        temp = *contacts;
        *contacts = (*contacts)->next;
        free(temp);
    }
    printf("程序已退出。");
}

//释放内存池
void releasePool() {
    struct Person *temp;

    while(pool != NULL) {
        temp = pool;
        pool = pool->next;
        free(temp);
    }
}

int main() {

    int code; // 程序指令代码
    struct Person *contacts = NULL;
    struct Person *person;

    printf("|- 欢迎使用通讯录管理程序 -|\n");
    printf("|--- 1:插入新的联系人 ---|\n");
    printf("|--- 2:查找已有联系人 ---|\n");
    printf("|--- 3:更改已有联系人 ---|\n");
    printf("|--- 4:删除已有联系人 ---|\n");
    printf("|--- 5:显示已有联系人 ---|\n");
    printf("|--- 6:退出通讯录程序 ---|\n");
    printf("|- Powered by Fishc.com -|\n");

    while(1) {
        printf("\n请输入指令代码:");
        scanf("%d", &code);
        switch(code) {
            case 1: addPerson(&contacts);
                break;
            case 2: person = findPerson(contacts); // 返回一个结构体,找不到联系人就返回空
                    if(person == NULL) {
                        printf("找不到该联系人! \n");
                    } else {
                        printPerson(person);
                    }
                break;
            case 3: changePerson(contacts);
                break;
            case 4: delPerson(&contacts); // 删除联系人
                break;
            case 5: displayContacts(contacts);
                break;
            case 6: goto END; // goto 语句实现语句的跳转,尽量少用,或者不用
        }
    }

END:
    releaseContacts(&contacts);
    releasePool();

    return 0;
}

基础typedef(给变量起别名)

  • 是C语言最重要的关键字之一
  • 是对一个类型的封装
# include <stdio.h>

typedef int integer;

int main() {

    integer a;
    int b;

    a = 520;
    b = a;

    printf("a = %d\n", a);
    printf("b = %d\n", a);
    printf("size of a = %d\n", sizeof(a));

    return 0;
}
  • 可以一次性起好几个别名
  • 可以是 变量名,也可以是一个指针
# include <stdio.h>

// 可以一次给一个变量起好几个别名,用“逗号”隔开
// 可以是 变量名,也可以是一个指针
typedef int INTEGER, *PIRINT;

int main() {

    INTEGER a = 520;
    PIRINT b,c;

    b = &a;
    c = b;

    printf("addr of a = %p\n", c);

    return 0;
}

typedef 与结构体紧密相关(很重要与数据结构紧密相关)

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

// 使用type给结构体去一个 别名 和 指针
// 别名 代替 struct Date 的反复书写
// 指针 用来代替用到机构体指针的地方
typedef struct Date {
    int year;
    int month;
    int day;
}DATE, *PDATE;

int main() {

    struct Date *date;

    // 未利用 typedef起别名和指针 之前
    // date = (struct Date *)malloc(sizeof(struct Date));

    // 利用 typedef起别名和指针 之后
    date = (PDATE)malloc(sizeof(DATE));

    if(date == NULL) {
        printf("内存分配失败!\n");
        exit(1);
    }

    date->year = 2022;
    date->month = 3;
    date->day = 12;

    printf("%d-%d-%d\n", date->year, date->month, date->day);

    return 0;
}

进阶typedef

  • 在编程中使用typedef目的一般有两个

    • 一个是给变量起一个容易记住且意义明确的别名;
    • 另一个是简化一些复杂的类型声明。
  • 数组指针

# include <stdio.h>

// 数组指针起别名
typedef int (*PTR_TO_ARRAY)[3];

// int (*ptr)[3]; ==》 typedef int (*PTR_TO_ARRAY)[3];
int main() {

    int array[3] = {1, 2, 3};
    PTR_TO_ARRAY ptr_to_array = &array;

    int i;
    for(i = 0; i < 3; i++) {
        // 先进行解引用然后在逐个进行便利
        printf("%d\n", (*ptr_to_array)[i]);
    }

    return 0;
}


  • 函数指针
# include <stdio.h>

typedef int (*PTR_TO_FUN)(void);

int fun(void) {
    return 520;
}

// 函数指针 int (*fun)(void) ==》 typedef int (*PTR_TO_FUN)(void)
int main() {

    PTR_TO_FUN ptr_to_fun = &fun;
    printf("%d\n", (*ptr_to_fun)());

    return 0;
}
  • 指针函数
# include <stdio.h>

/* 
 指针函数 int *(*array[3])(int); 
 ==》
    typedef int *(*PTR_TO_FUN)(int);
    PTR_TO_FUN array[3];
*/

typedef int *(*PTR_TO_FUN)(int); // 这样声明可以灵活的定义数组的长度
// 例如:PTR_TO_FUN array[3];

int *funA(int num) {
    printf("%d\t", num);
    return &num;
}

int *funB(int num) {
    printf("%d\t", num);
    return &num;
}

int *funC(int num) {
    printf("%d\t", num);
    return &num;
}

int main() {

    // 不加 & 也可以,因为函数名就是这个函数的地址
    PTR_TO_FUN array[3] =  {&funA, &funB, &funC};
    int i;
    for(i = 0; i < 3; i++) {
        printf("addr of num %p\n", (*array[i])(i));
    }

    return 0;
}
# include <stdio.h>

/* 
 void (*funA(int, void(*funB)(int)))(int);
 ==》
    typedef void (*PTR_TO_FUN)(void);
    PTR_TO_FUN funA(int, PTR_TO_FUN);
*/

typedef int (*PTR_TO_FUN)(int, int);

int add(int, int);
int sub(int, int);
/*

// typeof 简化前
int calc(int (*)(int, int), int, int);
int (*select(char op))(int, int);

 */
// typeof 简化后
int calc(int (*)(int, int), int, int);
int (*select(char op))(int, int);

int add(int num1, int num2) {
    return num1 + num2;
}

int sub(int num1, int num2) {
    return num1 - num2;
}

int calc(int (*fp)(int, int), int num1, int num2) {
    return (*fp)(num1, num2);
}

int (*select(char op))(int, int) {
    switch(op) {
        case '+': return add;
        case '-': return sub;
    }
}

int main() {

    int num1, num2;
    char op;
    int (*fp)(int, int);

    printf("请输入一个式子(如 1+3):");
    scanf("%d%c%d", &num1, &op, &num2);

    fp = select(op); // fp = select(op) 返回的是函数指针,所以用此来接收
    printf("%d %c %d = %d\n", num1, op, num2, calc(fp, num1, num2));
    return 0;
}

共用体

  • 共用体的成员拥有同一个内存 地址!
	union 共用体名称 {
		共用体成员1;
		共用体成员2;
		共用体成员3;
		......
	};
# include <stdio.h>
# include <string.h>


// 共用体,使用的是同一个地址。
// 在使用的时候只能使用其中一个
union Test {
    int i;
    double pi;
    char str[6];
};

int main() {
    
    union Test test;

    test.i = 520;
    test.pi = 3.14;
    // 拷贝字符串
    strcpy(test.str, "FishC");
	
	// 地址使用%p
    printf("addr of test.i: %p\n", &test.i);
    printf("addr of test.pi: %p\n", &test.pi);
    printf("addr of test.str: %p\n", &test.str);

    return 0;
}

枚举类型

  • 一 一列举
// 声明枚举类型
enum 枚举类型名称 {
	枚举值名称,
	枚举值名称,
	...
};

// 声明枚举变量
enum 枚举类型名称 枚举变量1, 枚举变量2;
# include <stdio.h>
# include <time.h>

int main() {
    // 枚举常量,默认 int 类型,从0开始
    enum Week {sun, mon, tue, wed, thu, fri, sat};
    // 枚举变量
    enum Week today;

    struct tm *p;
    time_t t;
    time(&t);
    p = localtime(&t);

    today = p->tm_wday;

    switch(today) {
        case mon:
        case tue:
        case wed:
        case thu:
        case fri:
            printf("干活!\n");
            break;
        case sat:
        case sun:
            printf("休息!\n");
            break;
        default:
            printf("");
    }

    
    return 0;
}

# include <stdio.h>

int main() {
	// 枚举常量,默认 int 类型,为常量,指定后不能改变值
    enum Color {red = 10, green, blue};
    enum Color rgb;

    for(rgb = red; rgb <= blue; rgb++) {
        printf("rgb is %d\n", rgb);
    }

    return 0;
}
# include <stdio.h>

int main() {
	
	// 如果从中间赋值,前面的部分还是会从 0 开始
    enum Color {red, green, yellow = 10, blue};
    enum Color rgb;

    printf("red = %d\n", red); // 0
    printf("green = %d\n", green); // 1
    printf("yellow = %d\n", yellow); // 10
    printf("blue = %d\n", blue); // 11

    return 0;
}

位域(位段,位字段)

  • 一般只有int类型使用位域
  • 可以把一个字节拆分成多个使用
# include <stdio.h>

int main() {

    // 通过位域,划分结构体  : 后面是指定划分了多少个字节
    struct Test {
        unsigned int a:1;
        unsigned int b:1;
        unsigned int c:2;
    };

    struct Test test;
    test.a = 0;
    test.b = 1;
    test.c = 2;

    printf("a = %d, b = %d, c = %d\n", test.a, test.b, test.c); // a = 0, b = 1, c = 2
    // size of test = 4   
    // 只用了四个字节
    printf("size of test = %d\n", sizeof(test));

    return 0;
}
  • 位域成员可以没有名称,只要给出数据类型和位宽即可(因为无名,所以不能直接使用)。
struct Test {
    unsigned int x:100;
    unsigned int y:200;
    unsigned int z:300;
    unsigned int  :424;
};

位操作

逻辑位运算符(对操作位中的每一位都起作用)
  • 按位取反(~),优先级最高
  • 按位与(&),优先级第二高
  • 按位异或(^),优先级第三高
  • 按位或(|),优先级最低
  • 和赋值号结合,除了按位取反,其他都可以和赋值号结合
# include <stdio.h>

int main() {
    // 十六进制数,以 0x 开头
    int mask = 0xFF;
    int V1 = 0xABCDEF;
    int V2 = 0xABCDEF;
    int V3 = 0xABCDEF;

    V1 &= mask; // V1 = V1 & mask;
    V2 |= mask;
    V3 ^= mask;

    printf("V1 = 0x%X\n", V1);
    printf("V2 = 0x%X\n", V2);
    printf("V3 = 0x%X\n", V3);

    return 0;
}

移位和位操作的应用(效率很高)

  • 左移位运算符 (<<)

    • 每次左移一位就相当于 乘以2
    • 目标数据 << 移动的位数
    • 11001010 << 2 ==》 00101000
  • 右移位运算符(>>)

    • 每次右移一位就相当于 除以2
    • 目标数据 << 移动的位数
  • 和赋值号相结合

# include <stdio.h>

int main() {
    int value = 1;

    while(value < 1024) {
        // 每次左移一位就相当于 乘以2 (效率要比直接乘以2要快的多)
        value <<= 1; // value = value << 1;
        printf("value = %d\n", value);
    }

    printf("\n...分割线\n");

    value = 1024;
    while(value > 0) {
        // 每次右移一位就相当于 除以2
        value >>= 2; // value = value >> 2;
        printf("value = %d\n", value);
    }

    return 0;
}

打开和关闭文件

文本文件 和 二进制文件(除文本文件之外的其他文件)
  • 打开和关闭文件***(文件操作完以后以后必须要关闭)***
# include <stdio.h>
# include <stdlib.h>

int main() {

    FILE *fp; // 声明文件类型的指针
    int ch; // 接收读取的文件

    // 用fopen打开文件,并且把指针地址给fp,再判断是不是空
    if((fp = fopen("hello_118.txt", "r")) == NULL) {
        printf("文件打开失败!");
        exit(EXIT_FAILURE); // 表示退出程序
    }

    while((ch = getc(fp)) != EOF) { // EOF 表示文件结束
        putchar(ch);
    }
    // 关闭文件,通过文件指针来进行传递
    fclose(fp); // 关闭成功返回非0值,关闭失败返回 EOF

    return 0;
}

读写文件1

  • 读写单个字符
    • 读取:fgetc(函数) 和 getc(宏)
    • 写入:fputc 和 putc // 字符,以文本的形式写入
# include <stdio.h>
# include <stdlib.h>

int main() {
    
    FILE *fp1;
    FILE *fp2;
    int ch;

    if((fp1 = fopen("hello_119.txt", "r")) == NULL) {
        printf("文件打开失败!\n");
        exit(EXIT_FAILURE);
    }

    if((fp2 = fopen("fishc_119.txt", "w")) == NULL) {
        printf("文件打开失败!\n");
        exit(EXIT_FAILURE);
    }

    while((ch = fgetc(fp1)) != EOF) {
        // 将 fp1 文件里面的数据写入 fp2 中
        fputc(ch, fp2);
    }

    fclose(fp1);
    fclose(fp2);

    return 0;
}
  • 读写整个字符串
    • 读取:fgets(函数) 和 gets(宏)
    • 写入:fputs 和 puts // 字符,以文本的形式写入
# include <stdio.h>
# include <stdlib.h>

# define MAX 1024

int main() {

    FILE *fp;
    char buffer[MAX];

    if((fp = fopen("lines_120.txt", "w")) == NULL) {
        printf("文件打开失败!");
        exit(EXIT_FAILURE);
    }

    fputs("Line one: Hello World! \n", fp); 
    fputs("Line two: Hello FishC! \n", fp); 
    fputs("Line one: I love FishC.com! \n", fp);   

    fclose(fp); // 写入数据完毕以后直接关闭
    // 需要读取的时候再次打开

    if((fp = fopen("lines_120.txt", "r")) == NULL) {
        printf("文件打开失败!");
        exit(EXIT_FAILURE);
    }

    while(!feof(fp)) { // feop 查找是否到了文件末尾
        fgets(buffer, MAX, fp);
        printf("%s", buffer);
    }

    return 0;
}

读写文件2

  • 格式化读写文件
    • fscanf 和 fprintf // 通过指针控制输入\输出的地方,不在输入到控制台
# include <stdio.h>
# include <stdlib.h>
# include <time.h>

int main() {

    FILE *fp;
    struct tm *p;
    time_t t;

    time(&t); // 获取时间值到 t 里面
    p = localtime(&t);

    if((fp = fopen("date_121.txt", "w")) == NULL) {
        printf("文件打开失败!");
        exit(EXIT_FAILURE);
    }
    
    fprintf(fp, "%d - %d - %d", 1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday);
    
    fclose(fp);

    int year, month, day;
    if((fp = fopen("date_121.txt", "r")) == NULL) {
        printf("文件打开失败!");
        exit(EXIT_FAILURE);
    }
    
    fscanf(fp, "%d - %d - %d", &year, &month, &day);

    printf("%d - %d - %d", year, month, day);

    fclose(fp);
    return 0;
}
# include <stdio.h>
# include <stdlib.h>

int main() {

    FILE *fp;
    
    // wb 二进制写入模式
    if((fp = fopen("text_122.txt", "wb")) == NULL) {
        printf("文件打开失败!");
        exit(EXIT_FAILURE);
    }
    
    fputc('5', fp);
    fputc('2', fp);
    fputc('0', fp);
    fputc('\n', fp);

    fclose(fp);
    return 0;
}
  • fread 和 -fwrite // 以二进制的形式写入
# include <stdio.h>
# include <stdlib.h>
# include <string.h>

struct Date {
    int year;
    int month;
    int day;
};

struct Book {
    char name[40];
    char author[40];
    char publisher[40];
    struct Date date;
};


int main() {

    FILE *fp;
    struct Book *book_for_write, *book_for_read;

    book_for_write = (struct Book *)malloc(sizeof(struct Book));
    book_for_read = (struct Book *)malloc(sizeof(struct Book));
    
    if(book_for_read == NULL || book_for_write == NULL) {
        printf("内存分配失败!\n");
        exit(EXIT_FAILURE);
    }

    strcpy(book_for_write->name, "《带你学C带你飞》");
    strcpy(book_for_write->author, "小甲鱼");
    strcpy(book_for_write->publisher, "清华大学出版社");
    book_for_write->date.year = 2022;
    book_for_write->date.month = 3;
    book_for_write->date.day = 13;

    if((fp = fopen("file_123.txt", "w")) == NULL) {
        printf("文件打开失败!");
        exit(EXIT_FAILURE);
    }
    
    fwrite(book_for_write, sizeof(struct Book), 1, fp);
    fclose(fp);

    if((fp = fopen("file_123.txt", "r")) == NULL) {
        printf("文件打开失败!");
        exit(EXIT_FAILURE);
    }

    fread(book_for_read, sizeof(struct Book), 1, fp);
    printf("书名:%s\n", book_for_read->name);
    printf("作者:%s\n", book_for_read->author);
    printf("书名:%s\n", book_for_read->publisher);
    printf("出版日期:%d - %d -%d\n", book_for_read->date.year, book_for_read->date.month, book_for_read->date.day);

    fclose(fp);
    return 0;
}

随机读写文件

  • 获取位置指示器的值(ftell)
# include <stdio.h>
# include <stdlib.h>

int main() {

    FILE *fp;

    if((fp = fopen("hello_124.txt", "w")) == NULL) {
        printf("文件打开失败!");
        exit(EXIT_FAILURE);
    }

    printf("%ld\n", ftell(fp)); //打印出位置指示器的值
    fputc('F', fp);
    printf("%ld\n", ftell(fp)); //打印出位置指示器的值
    fputc('C', fp);
    printf("%ld\n", ftell(fp)); //打印出位置指示器的值

    fclose(fp);

    return 0;
}

-rewind 将位置指示器调整到文件的头部

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

int main() {

    FILE *fp;

    if((fp = fopen("hello_124.txt", "w")) == NULL) {
        printf("文件打开失败!");
        exit(EXIT_FAILURE);
    }

    printf("%ld\n", ftell(fp)); //打印出位置指示器的值
    fputc('F', fp);
    printf("%ld\n", ftell(fp)); //打印出位置指示器的值
    fputc('C', fp);
    printf("%ld\n", ftell(fp)); //打印出位置指示器的值

    rewind(fp);
    fputs("Hello", fp);

    fclose(fp);

    return 0;
}
  • fseek 找到文件中特定的位置
# include <stdio.h>
# include <stdlib.h>

# define N 4

struct Stu{
    char name[24];
    int num;
    float score;
}stu[N], sb;

int main() {

    FILE *fp;
    int i;

    if((fp = fopen("sorce_125.txt", "wb")) == NULL) {
        printf("打开文件失败! \n");
        exit(EXIT_FAILURE);
    }

    printf("请开始录入成绩(格式:姓名 学号 成绩)\n");
    for(i = 0; i < N; i++) {
        //              字符串不需要加 & ,其他的要加
        scanf("%s %d %f", stu[i].name, &stu[i].num, &stu[i].score);
    }

    fwrite(stu, sizeof(struct Stu), N, fp);
    fclose(fp);


    if((fp = fopen("sorce.txt", "rb")) == NULL) {
        printf("打开文件失败! \n");
        exit(EXIT_FAILURE);
    }

    // 向后移动一个 结构体的位置 这样就可以找到第二个 数据的位置
    fseek(fp, sizeof(struct Stu), SEEK_SET);
    fread(&sb, sizeof(struct Stu), 1, fp);
    printf("%s(%d)的成绩是:%.2f\n", sb.name, sb.num, sb.score);

    fclose(fp);

    return 0;
}

标准流和错误处理

  • 文件流
    • 标准输入(stdin)
    • 标准输出(stdout)
    • 标准错误输出(stderr)
# include <stdio.h>
# include <stdlib.h>

int main() {

    FILE *fp;

    if((fp = fopen("yagenjiubucunzaidewenjian.txt", "r")) == NULL) {
        fputs("文件打开失败!\n", stderr); //标准错误输出(stderr)
        exit(EXIT_FAILURE);
    }

    // do something...

    fclose(fp);

    return 0;
}
  • 重定向

    • 重定向标准输入使用 <
    • 重定向标准输出使用 >
    • 重定向标准错误输出使用 2>
  • 错误处理

    • 错误指示器 —— ferror
    • ferror 只会检测出错了,不显示出了什么错误

IO缓冲区

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

int main() {

    FILE *fp;
    if((fp = fopen("output.txt", "w")) == NULL) {
        perror("文件打开失败,原因是:");
        exit(EXIT_FAILURE);
    }
    fputs("I love FishC.com!\n", fp);
    getchar(); // 主程序,不输入数据程序就会卡在这里
    // fputs 输入的数据也会停在缓冲区,不会写入到文件中

    fclose(fp);

    return 0;
}
  • 标准IO提供的三种类型的缓冲模式:
    • 按块缓存
    • 按行缓存
    • 无缓存
# include <stdio.h>
# include <string.h>

int main() {
    char buff[1024];

    memset(buff, '\0', sizeof(buff));
    // _IOFBF 有缓冲区
    // _IONBF 没有缓冲区
    setvbuf(stdout, buff, _IONBF, 1024);

    fprintf(stdout, "Welcome to bbs.fishc.com\n");

    fflush(stdout);

    fprintf(stdout, "输入任意字符后才会显示该行字符!\n");

    getchar();

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值