C基础总结

C语言

C 语言是一种通用的、面向过程式的计算机程序设计语言。1972 年,为了移植与开发 UNIX 操作系统,丹尼斯·里奇在贝尔电话实验室设计开发了 C 语言。


使用的 IDE 为 Visual Studio 2022

第一节 基础语法

  1. C的令牌(token):分号,注释,标识符,关键字,空格

  2. 数据类型

    1. 在 C 语言中,数据类型指的是用于声明不同类型的变量或函数的一个广泛的系统。变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。

      • 基本数据类型:整型(int)、字符型(char)、浮点型(float)、双精度浮点型(double)
      • 枚举类型(enum)
      • void类型
      • 派生类型:数组([])、指针(*)、结构体(struct)、共用体(union)
    2. 变量:是程序可操作的存储区的名称。变量声明向编译器保证变量以指定的类型和名称存在。

    3. 常量

      1. 是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。
      2. 在 C 中,有两种简单的定义常量的方式:
      • 使用 #define 预处理器: #define 可以在程序中定义一个常量,它在编译时会被替换为其对应的值。#define PI 3.14159
      • 使用 const 关键字:const 关键字用于声明一个只读变量,即该变量的值不能在程序运行时修改。const int MAX_VALUE = 100;
  3. 存储类:存储类定义 C 程序中变量/函数的的存储位置、生命周期和作用域。

    1. register 寄存器(基本不用),可修饰变量(注:cpu在寄存器中拿数据快;因为寄存器比较宝贵,所以不能定义寄存器数组;其只能修饰字符型和整型;不能对寄存器变量取地址,因为只有存放在内存中的数据才有地址)
    2. static静态,修饰全局变量、局部变量、函数
    3. const常量,修饰的变量是只读的,不能修改其值。
    4. auto自动(基本不用)
    5. extern外部,一般用于函数和全局变量的声明
  4. 运算符

    1. 算数运算符:+, -, *, /, %, ++, –
    2. 关系运算符:==, !=, >, <, >=, <=
    3. 逻辑运算符:&&, ||, !
    4. 位运算符:&, |, ^, ~, <<, >>
    5. 赋值运算符
    6. 杂项运算符:
      运算符描述实例
      sizeof()返回变量的大小。sizeof(a) 将返回 4,其中 a 是整数。
      &返回变量的地址。&a; 将给出变量的实际地址。
      *指向一个变量。*a; 将指向一个变量。
      ? :条件表达式如果条件为真 ? 则值为 X : 否则值为 Y
    7. 优先级:!>算数运算符>关系运算符>&&和||>赋值运算符
  5. 判断结构:(1)if else;(2)switch case default;(3)? : ;

案例:
5.1 条件运算符(判断字母大小写)

//判断字母是否为大写,若为大写,则转小写,否则原样输出
printf("请输入一个字母:");
char ch;
scanf_s("%c", &ch);
ch = (ch >= 'A' && ch <= 'Z') ? ch + 32 : ch;
printf("%c", ch);

printf('\n');
system("pause");
return 0;

5.2 判断闰年

//判断闰年:4整除且100不整除,或整除400
int year;
printf("输入一个年份:");
scanf_s("%d", &year);
if ((year % 4 == 0) && (year % 100 != 0) || (year % 400 == 0))
    printf("%d是一个闰年", year);
else
    printf("%d不是一个闰年", year);

5.3 求阶二元方程

 //求解ax^2+bx+c=0方程
 #include <stdio.h>//io输入输出
 #include <math.h>//数学计算
 #include <stdlib.h>//system()
 
int main()
{
    double a, b, c, disc, x1, x2, realpart, imagpart;
    printf("请输入a, b, c的值:");
    scanf_s("%lf %lf %lf", &a, &b, &c);

    printf("该等式");
    if (fabs(a) < 1e-6)//1e-6: 1*10^-6,用于判断浮点数是否等于0
        printf("不是二次方程!");
    else
    {
        disc = b * b - 4 * a * c;
        if (fabs(disc) < 1e-6)
        {
            x1 = x2 = -b / (2 * a);
            printf("有两个相同的实根:x1 = x2 = %-8.2f", x1);
        }
        else if (disc > 0)
        {
            x1 = (-b + sqrt(disc)) / (2 * a);
            x2 = (-b - sqrt(disc)) / (2 * a);
            printf("有两个不同的实根:x1 = %-8.2f, x2 = %-8.2f", x1, x2);
        }
        else
        {
            realpart = -b / (2 * a);
            imagpart = sqrt(-disc) / (2 * a);
            printf("有一对共轭复根:x1, x2 = %-8.2f±%-8.2fi", realpart, imagpart);
        }
    }
    printf("\n");
    system("pause");
    return 0;
}

5.4 输出正整数的各个位数

//输出一个正整数的各个位数
int num, indiv, ten, hundred, thousand, ten_thousand, place;
printf("请输入一个1~99999的整数:");
scanf_s("%d", &num);
//判断是几位数
if (num > 9999)
    place = 5;
else if (num > 999)
    place = 4;
else if (num > 99)
    place = 3;
else if (num > 9)
    place = 2;
else
    place = 1;
printf("%d是%d位数\n", num, place);
indiv = num % 10;
ten = num / 10 % 10;
hundred = num / 100 % 10;
thousand = num / 1000 % 10;
ten_thousand = num / 10000 % 10;
printf("反序输出每个位数:%d %d %d %d %d \n", indiv, ten, hundred, thousand, ten_thousand);

5.5 判断星期(switch)

//判断星期
int week;
printf("请输入星期:");
scanf_s("%d", &week);
switch (week)
{
case(1):printf("今天星期一"); break;
case(2):printf("今天星期二"); break;
default:
    printf("今天不是星期一也不是星期二");
    break;
}
  1. 循环结构: while, do while, for
    1. while(判断条件) {循环体} 先判断,后执行,在循环体中应有使循环趋向于结束的语句。
    2. do {循环体} while(判断条件);注意分号,先执行,后判断。只要第一步必须执行,则while和do while没有差别。
    3. for(初始条件;循环条件;循环变量增值) {循环体}
    4. 循环的嵌套(改变循环执行的状态)
      1. break:提前终止循环
      2. continue: 提前结束本次循环

案例:
6.1 求累加

//求累加
int i = 1, num = 0,x;
printf("请输入一个大于0的整数:");
scanf_s("%d", &x);
while (i <= x)
{
    num = num + i;
    ++i;
}
printf("%d累加的结果是%d", x, num);

6.2 不能被3整除的数

//不能被3整除的数
int n;
for (n = 1; n <= 100; n++)
{
    if (n % 3 == 0)
        continue;
    printf("%d ", n);
}

6.3 输出矩阵

//输出4*5的矩阵
int n = 1;
for (int i = 0; i < 4; i++)
{
    for (int j = 0; j < 5; j++)
    {
        printf("%d ", n);
        n++;
    }
    printf("\n");
}

6.4 兔生兔问题

//兔生兔问题:斐波那契数列 1 1 2 3 5 8 ……
int f1 = 1, f2 = 1, f3;
printf("%d %d ", f1, f2);
for (int i = 0; i < 18; i++)
{
    f3 = f1 + f2;
    printf("%d ", f3);
    f1 = f2;
    f2 = f3;
}

6.5 求素数

//输出100~200的素数(质数)
int i,index=0;
for (int n = 101; n < 200; n+=2)
{
    int k = sqrt(n);
    for (i = 2; i <= k; i++)//k向下取整,所以需要i<=k
        if (n % i == 0) break;
    if (i > k)//判断
    {
        printf("%d ", n);
        index++;//计数
    }
    if (index % 10 == 0)  printf("\n");//10个一换行
}

6.6 求最大公约数

//求最大公约数和最小公倍数
int m, n, p, r, temp;
printf("请输入两个整数:");
scanf_s("%d %d", &m, &n);
p = m * n;
if (n < m)
{//n为较大者
    temp = n; n = m; m = temp;
}
while (m != 0)
{//辗转相除法
    r = n % m; n = m; m = r;
}
printf("最大公约数为:%d\n", n);
printf("最小公倍数为:%d\n", p / n);

6.7 求水仙花数

//输出水仙花数(一个三位数等于各位数字立方和)
int i, j, k, n;
for (n = 100; n < 1000; n++)
{
    i = n % 10;
    j = n / 10 % 10;
    k = n / 100;
    if (n == (pow(i, 3) + pow(j, 3) + pow(k, 3)))
        printf("%d ", n);
}

6.8 猴子吃桃问题

//猴子吃桃问题(每天吃一半多一个桃子,第10天只剩下一个桃子)
int x1 = 1, x2 = 1;
for (int day = 9; day > 0; day--)
{
    x2 = (x1 + 1) * 2;//倒推
    x1 = x2;
}
printf("原有%d个桃子", x2);//1534

第二节 数组、函数、作用域、枚举

  1. 数组(array), 下标(subscript)
    1. 数组是一组有序数据的集合,数组中的每一个元素都属于同一数据类型。
    2. 声明,初始化,访问
    3. 字符数组
      1. C语言中,没有字符串类型,也没有字符串变量,字符串是存放在字符型数组中的。
      2. 字符串结束标志:‘\0’(如果一个字符数组中包含一个以上’\0’,则遇到第一个’\0’时输出就结束)
    4. 字符串处理函数
      1. 输出:puts(str); 输入:gets(str)。注:用putsgets函数只能输出或输入一个字符串。
      2. 连接:stract(str1,str2), 将str2续接到str1的后面, 返回str1的地址。
      3. 复制:strcpy(str1,str2), 将str2复制到str1中; strncpy(str1,str2,n), 将str2中最前面的n个字符复制到str1中。注:不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组,字符数组名是一个地址常量,它不能改变值。
      4. 比较:strcmp(str1,str2), 按ASCII码值大小比较, 若相同,返回0,若str1 > str2,返回正整数,若str1 < str2,返回负整数。
      5. 测字符串长度:strlen(str), 返回字符串的实际长度, 不包括’\0’在内。
      6. 转换大小写:_strupr(str)转大写, _strlwr(str)转小写。
      7. 使用字符串处理函数时,需要包含头文件#include <string.h>

案例:
1.1 使用数组

//初始化数组
int arr[] = { 1,2,3,4 };

//访问数组元素
printf("数组的第一个元素arr[0]=%d\n", arr[0]); //1

//获取数组长度
int length = sizeof(arr) / sizeof(arr[0]);
printf("数组的长度为%d\n", length); //4

//多维数组(二维数组)
int arr2[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
for (int i = 0; i < 3; i++)
    for (int j = 0; j < 4; j++)
        printf("a[%d][%d]=%d\n", i, j, arr2[i][j]);

1.2 字符数组

//字符数组
char c[] = "china.";
printf("%s\n", c);//字符串输出
char c2[5];
//字符串输入,不加&,因为在C语言中数组名就代表该数组的起始地址
scanf_s("%s", c2,5);//防止越界报错
printf("%s", c2);

1.3 冒泡排序法

//数组--冒泡排序法
int arr[] = { 23,54,12,22,44,21,64,46 };
int length = sizeof(arr) / sizeof(arr[0]);

for (int i = 0; i < length - 1; i++)//循环n-1次
{
    for (int j = 0; j < length - 1 - i; j++)//每次从n-i中找最大的:大的下沉,小的上浮
    {
        int temp;
        if (arr[j] > arr[j + 1])//注意:j<length-1-i,因为arr[j]与arr[j+1]比较
        {
            temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp;
        }
    }
}

//输出
printf("排序后的数组为:\n"); //12 21 22 23 44 46 54 64
for (int i = 0; i < length; i++)
    printf("%d ", arr[i]);

1.4 选择排序法

//数组--选择排序法
int arr[] = { 23,54,12,22,44,21,64,46 };
int length = sizeof(arr) / sizeof(arr[0]);

for (int i = 0; i < length-1; i++)//最后一个不用再比较了,也是循环n-1次
{
    int temp, min = i;//记录最小值的下标
    for (int j = i + 1; j < length; j++)//j从i+1开始和min(=i)比较
        if (arr[min] > arr[j]) min = j;
    if (min != i)//每次找到最小值
        {
            temp = arr[i]; arr[i] = arr[min]; arr[min] = temp;
        }
}
//输出
printf("排序后的数组为:\n");//12 21 22 23 44 46 54 64
for (int i = 0; i < length; i++)
    printf("%d ", arr[i]);

1.5 矩阵行列互换

//将矩阵(二维数组)行列互换
int a[2][3] = { {1,2,3} ,{4,5,6} };
int b[3][2] = { 0 };

printf("数组a:\n");
for (int i = 0; i < 2; i++)
{
    for (int j = 0; j < 3; j++)
    {
        printf("%d ", a[i][j]);//输出a
        b[j][i] = a[i][j];//赋值给b
    }
    printf("\n");
}
printf("数组b:\n");
for (int i = 0; i < 3; i++)
{
    for (int j = 0; j < 2; j++)
        printf("%d ", b[i][j]);//输出b
    printf("\n");
}

1.6 统计单词

//统计单词
char string[81];
int i, num = 0, word = 0;//num记录单词数,word记录前一个字符的状态
char c;
gets(string);//输入字符串
for (i = 0; (c = string[i]) != '\0'; i++)//是'\0'则结束
{
    if (c == ' ') word = 0;//是空格字符,使word置0
    else if (word == 0)
    {
        word = 1;//word不是空格字符且原值为0,使word置1
        num++;//num累加,即增加一个新单词
    }
}
printf("包含单词数为 %d", num);  
//i am a lucky dog
//包含单词数为 5

1.7 数组逆序

//数组逆序存放
int arr[] = { 1,2,3,4,5,6,7,8,9 };
int length = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < length / 2; i++)
{
    int temp = arr[i];
    arr[i] = arr[length - 1 - i];//对称元素交换
    arr[length - 1 - i] = temp;
}
for (int i = 0; i < length; i++)
    printf("%d ", arr[i]);//输出

1.8 杨辉三角形

 #include <stdio.h>
 #include <math.h>
 #include <stdlib.h>
 #include <string.h>
 #define N 10 // 宏定义:输出层数

int main()
{
    //6. 杨辉三角形
    /*      1-------1
    *      1 1------2
    *     1 2 1-----3
    *    1 3 3 1----4
    *   1 4 6 4 1---5
    */
    int a[N][N],i,j;
    for (int i = 0; i < N; i++)
    {//第一列和对角线都为1
        a[i][0] = 1;
        a[i][i] = 1;
    }
    for (i = 2; i < N; i++)
    {//从第3行开始处理
        for (j = 1; j < i ; j++)
        {//由上一行的两个元素相加求和
            a[i][j] = a[i - 1][j - 1] + a[i - 1][j];
        }
    }
    for (i = 0; i < N; i++)
    {
        for (int k = 0; k < (N - i) ; k++) printf(" ");
        for (j = 0; j <= i; j++)   
            printf("%d ", a[i][j]);

        printf("\n");//不理解,我用单引号括个换行符,你在报什么错,无语,换成双引号就好了。。
    }

    printf("\n");
    system("pause");
    return 0;
}
  1. 函数概念
    1. 用函数实现模块化程序设计,基本思想:自顶向下,逐步分解,分而治之。

    2. 函数就是功能。

    3. 函数注意事项:

      • 函数不能嵌套定义,主函数中可以声明其他函数。
      • C程序从main函数开始,从main函数结束。
      • 函数之间可以互相调用,但是其他函数不可调用主函数。
    4. 定义函数:返回值类型,形参类型

    5. 函数的递归调用:“递推"和"回溯”

    6. 数组与函数:数组名就是数组的首地址,所以是传址方式。

案例:
2.1 汉诺塔问题

//汉诺塔问题(递归)
void move(int n, char a, char b, char c)
{//n个盘子,3个柱子,将n个盘子借助b移到c,一次只能移动一个盘子
    if (n == 1) printf("%c->%c\n", a, c);//当n只有1个的时候,直接从a移到c
    else
    {
        move(n - 1, a, c, b);//把a的n-1个盘子通过c移到b
        printf("%c->%c\n", a, c);//把a的最后1个盘子(最大的盘)移到c
        move(n - 1, b, a, c);//把b上面的n-1个盘子通过a移到c
    }
}
int main()
{
    int n;
    printf("请输入要移动的块数:");
    scanf_s("%d", &n);
    move(n, 'a', 'b', 'c');


    printf("\n");
    system("pause");
    return 0;
}

2.2 递归求阶乘

//递归求阶乘
int fac(int n)
{
    if (n < 0)
    {
        printf("参数输入错误!");
        return 0;
    }
    if (n == 0 || n == 1) return 1;//n=0|1时,n!=1
    else return fac(n - 1) * n;//n>1时,n!=n*(n-1)!
}
int main()
{
    int n,result;
    printf("请输入阶乘的参数:");
    scanf_s("%d", &n);
    result = fac(n);
    (result != 0) ? printf("%d!=%d", n, result) : printf(" ");//参数判断

    printf("\n");
    system("pause");
    return 0;
}

2.3 数组与函数求平均成绩

//数组与函数
float average(int arr[], int size)
{//求平均成绩
    float sum = 0;
    for (int i = 0; i < size; i++)
        sum += arr[i];
    return sum / size;
}

int main()
{
    int arr[] = { 87,65,98,56,79,99 };
    int length = sizeof(arr) / sizeof(arr[0]);
    float aver = average(arr, length);//传入数组名
    printf("平均成绩为%6.2f", aver);

    printf("\n");
    system("pause");
    return 0;
}

2.4 写一个函数,将两个字符串拼接

//写一个函数,将两个字符串拼接
void concatenate(char str1[], char str2[], char str_concat[]);//声明

int main(void)
{
    printf("请输入两个字符串:\n");
    char str1[100], str2[100], str_concat[200];
    scanf_s("%s", str1, sizeof(str1)); // 输入第一个字符串,防止写入异常
    scanf_s("%s", str2, sizeof(str2)); // 输入第二个字符串
    concatenate(str1, str2, str_concat);
    printf("%s和%s的拼接结果为\n%s", str1, str2, str_concat);
 
    printf("\n");
    system("pause");
    return 0;
}

void concatenate(char str1[], char str2[], char str_concat[])//定义
{
    int i, j;
    for (i = 0; str1[i] != '\0'; i++)
        str_concat[i] = str1[i];
    for (j = 0; str2[j] != '\0'; j++)
        str_concat[i + j] = str2[j];
    str_concat[i + j] = '\0'; // 添加空字符 '\0',表示字符串结束
}

2.5 写一个函数,将一个字符串中的元音复制到另一字符串,并输出

//写一个函数,将一个字符串中的原因字母复制到另一个字符串中,并输出
void get_vowel(char str[],char vowel[]);//声明
void stringToLower(char str[]);

int main(void)
{
    printf("请输入一个字符串: ");
    char str[100], vowel[100];
    scanf_s("%s", str, sizeof(str)); 
    get_vowel(str,vowel);
    printf("转换为小写字符串: %s\n其中的元音有: %s", str, vowel);
 
    printf("\n");
    system("pause");
    return 0;
}
void get_vowel(char str[], char vowel[])
{
    stringToLower(str);
    int i, j;
    for (i = 0,j=0; str[i] != '\0'; i++) //遍历str
        if (str[i] == 'a' || str[i] == 'o' || str[i] == 'e' || str[i] == 'i' || str[i] == 'u')
        {
            vowel[j] = str[i];//是元音,则填入,并将下标后移
            j++;
        }
    vowel[j] = '\0';
}
void stringToLower(char str[]) {//将字符串转为小写
    for (int i = 0; str[i] != '\0'; i++) {
        str[i] = tolower(str[i]);//库函数只能将一个字母字符转换为小写
    }
}

2.6 写一个函数,统计字符串中的字母、数字、空格、其他字符

//写一个函数,统计字符串中的字母、数字、空格、其他字符
int letter, digit, space, others;//全局变量
void count(char str[]);

int main(void)
{
    char text[100];
    printf("input string:\n");
    gets(text);//输入字符串
    printf("string:\n");
    puts(text);//输出字符串
    letter = 0; digit = 0; space = 0; others = 0;//初始化全局变量
    count(text);//调用函数
    printf("\nletter:%d\ndigit:%d\nspace:%d\nothers:%d", letter, digit, space, others);
 
    printf("\n");
    system("pause");
    return 0;
}
void count(char str[])
{
    int i;
    for (i = 0; str[i] != '\0'; i++)
    {
        if ((str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= 'a' && str[i] <= 'z'))
            letter++;
        else if (str[i] >= '0' && str[i] <= '9')
            digit++;
        else if (str[i] == ' ')
            space++;
        else
            others++;
    }
}

2.7 写一个函数,输入4个数字,输出时在两个数字间添加一个空格

//写一个函数,输入4个数字,输出时在两个数字间添加一个空格
void insert(char str[]);

int main(void)
{
    char str[60];
    printf("input four digits:");
    scanf_s("%s", str, sizeof(str));
    insert(str);
 
    printf("\n");
    system("pause");
    return 0;
}
void insert(char str[])
{
    int i;
    for (i = strlen(str); i > 0; i--)
    {//倒序填充,防止覆盖
        str[2 * i] = str[i];
        str[2 * i - 1] = ' ';
    }
    printf("output four digits:%s", str);
}

2.8 写一个函数,输入一行字符,将此字符串中最长的单词输出

//写一个函数,输入一行字符,将此字符串中最长的单词输出
int alphabetic(char c);//判断是否为字母字符
int longest(char string[]);//返回最长单词的起始下标

int main(void)
{
    char line[100];
    printf("input one line :");
    gets(line);//输入一行字符串
    int place = longest(line);//获得最长单词的起始下标
    printf("The longest word is :");
    for (int i =place; alphabetic(line[i]); i++)//输出最长字符串
        printf("%c", line[i]);

    printf("\n");
    system("pause");
    return 0;
}
int alphabetic(char c)
{
    if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
        return 1;
    else
        return 0;
}
int longest(char string[])
{
    int len = 0, length = 0;
    int point = 0, place = 0;
    int flag = 1;//单词开始
    for (int i = 0; i<=strlen(string); i++) //遍历字符串
    {//i<=strlen(string),具有 等于号 是为了将单词读完然后判断长度,不然到最后一个单词只记录了字母字符数量,而没有比较
        if(alphabetic(string[i]))//判断是否为字母,用于区分是否是属于一个单词
            if (flag)
            {
                point = i;//当前单词起始下标
                flag = 0;//将标志置0
                len = 1;
            }
            else
            {
                len++;//记录当前单词的长度
            }
        else
        {
            flag = 1;//下一个新单词,将标志置1
            if (len >= length) 
            {
                length = len;//最长单词长度
                place = point;//最长单词起始下标
                len = 0;//重置当前单词长度
            }
        }
    }
    printf("longest is %d\n", length);//输出最长单词的长度
    return place;
}

2.9 写一个函数,输入一个十六进制数,输出相应的十进制数

//写一个函数,输入一个十六进制数,输出相应的十进制数
int hex_to_d(char s[]);

int main(void)
{
    char hex_str[MAX], c;
    int flag_con=1, flag_hex=0;//标志:是否继续输入,是否是十六进制数
    int i = 0;//十六进制数起始下标,置0
    printf("input a hex number: ");
    while ((c = getchar()) != '\0' && i < MAX && flag_con)
    {
        if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))
        {//正确输入
            flag_hex = 1;
            hex_str[i++] = c;
        }
        else if (flag_hex)
        {//已有十六进制数
            hex_str[i] = '\n';//添加结束符
            printf("decimal number: %d\n", hex_to_d(hex_str));//转换进制
            printf("continue or not?");//是否继续输入
            c = getchar();
            if (c == 'n' || c == 'N')
                flag_con = 0;
            else
            {
                flag_hex = 0;//置0,表示重新输入十六进制数
                i = 0;
                printf("\ninput a hex number: ");
            }
        }
    }
    printf("\n");
    system("pause");

    return 0;
}
int hex_to_d(char s[])
{
    int n = 0;
    for (int i = 0; s[i] != '\0'; i++)
    {//十六进制转换十进制(技巧)
        if (s[i] >= '0' && s[i] <= '9')
            n = n * 16 + s[i] - '0';
        else if (s[i] >= 'A' && s[i] <= 'F')
            n = n * 16 + s[i] - 'A' + 10;
        else if (s[i] >= 'a' && s[i] <= 'f')
            n = n * 16 + s[i] - 'a' + 10;
    }
    return n;
}

2.10 用递归法将一个整数n转换成字符串

//用递归法将一个整数n转换成字符串
void convert(int n);

int main(void)
{
    int number;
    printf("input an integer: ");
    scanf_s("%d", &number);
    printf("output: ");
    if (number < 0)
    {
        number = -number;//如果是负数,则转为整数,输出负号
        putchar('-'); putchar(' ');//32
    }
    convert(number);//转为字符串
   
    printf("\n");
    system("pause");
    return 0;
}
void convert(int n)
{
    int i;
    if ((i = n / 10) != 0)//递推到最高位时,停止,并开始回溯
        convert(i);//在每一层递归中,先递归调用convert函数处理更高位的数字,再输出当前位的数字。
    putchar(n % 10 + '0');//输出每层的个位数
    putchar(32);//输出空格
}

2.11 给出年月日,计算该日是该年的第几天

//给出年月日,计算该日是该年的第几天
int leap(int year);
int sum_day(int month, int day);

int main(void)
{
    int year, month, day;
    int days = 0;
    printf("input date(year,month,day):");
    scanf_s("%d,%d,%d", &year, &month, &day);
    days = sum_day(month, day);
    if (leap(year) && month > 3)//是闰年且月份大于3的天数+1
        days = days + 1;
    printf("%d/%d/%d is the %dth day in this year.", year, month, day, days);
    
    printf("\n");
    system("pause");
    return 0;
}
int sum_day(int month, int day)
{
    int day_tab[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    for (int i = 1; i < month; i++)
        day += day_tab[i];//日份+月份=天数
    return day;
}
int leap(int year)
{
    return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0);
}
  1. 作用域
    1. 不同的作用域
      1. 在函数或块内部的局部变量
      2. 在所有函数外部的全局变量
      3. 在形式参数的函数参数定义中
    2. 存储空间分为:
      1. 程序区
      2. 静态存储区:全局变量
      3. 动态存储区:形参,局部变量无static的
    3. 局部变量的存储类别
      1. 自动变量(auto)
      2. 静态变量(static):保存上一次函数调用结束时的值
      3. 寄存器变量(register)
    4. 动态内存分配与指向它的指针变量
      1. 栈区(stack):非静态的局部变量(包括形参)是分配在内存中的动态存储区的,该区称为栈区,由编译器自动分配释放。
      2. 堆区(heap):向系统申请所需大小的空间,手动开辟释放,由于未在声明部分定义它们为变量或数组,因此不能通过变量名或数组名去引用这些数据,只能通过指针来引用。
      3. malloc函数开辟动态存储区:void *malloc(unsigned int size);,如果分配不成功,返回NULL.
      4. calloc函数开辟动态存储区:void *calloc(unsigned n, unsigned size);,一般用于分配空间较大。
      5. realloc函数重新分配动态存储区:void *realloc(void *p,unsigned int size);,用realloc函数将p所指向的动态空间的大小改变为size, p的值不变。
      6. free函数释放动态存储区:void free(void *p);
      7. 声明在<stdlib.h>头文件中
      8. void指针类型:空类型指针(无指向的地址),在向该存储区存放数据时就需要进行地址的类型转换,如pt=(int*)malloc(100)

案例:
3.1 建立动态数组,输入5个学生的成绩,另外用一个函数检查其中有无低于60分的,输出不及格的成绩

//建立动态数组,输入5个学生的成绩,另外用一个函数检查其中有无低于60分的,输出不及格的成绩
void check(int* p);
int main(void)
{
    int* p;
    p = (int*)malloc(5 * sizeof(int));//开辟动态存储区,并类型转换
    for (int i = 0; i < 5; i++)
        scanf("%d", p + i);
    check(p);

    printf("\n");
    system("pause");
    return 0;
}
void check(int* p)
{
    printf("They are fail: ");
    for (int i = 0; i < 5; i++)
        if (p[i] < 60) printf("%d ", p[i]);//数组名即地址,啊呀,已经要说烂了。。
}

3.2

  1. 枚举
    1. 枚举是 C 语言中的一种基本数据类型,用于定义一组具有离散值的常量。使数据更简洁易读。
    2. 定义格式:enum 枚举名 {枚举元素1,枚举元素2,……};
    3. 在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的。
    enum DAY //枚举类型
    {//枚举星期
        MON=1, TUE, WED, THU, FRI, SAT, SUN
    };
    enum DAY day;//枚举变量
    
关于总是出现的这个异常

这个异常(0xC0000005)通常称为访问冲突或访问违规。它表示程序试图访问一个无效的内存地址,或者尝试向只读内存地址写入数据,导致操作系统拒绝访问,并引发异常。

造成该异常的原因可能有很多,常见的情况包括:

  1. 空指针引用:程序试图使用一个空指针进行读取或写入操作。
  2. 野指针:指针没有正确初始化,或者已经被释放,但仍然被引用。
  3. 数组越界:程序访问了超出数组边界的位置。
  4. 非法内存访问:程序访问了未分配或已释放的内存。
  5. 内存泄漏:未正确释放已分配的内存,导致内存耗尽。
  6. 多线程竞争:多个线程同时访问和修改共享数据,导致访问冲突。

要解决这个异常,需要仔细检查出现异常的代码,并进行逐步调试和排查。可以通过以下方法来定位问题:

  1. 检查空指针:确保所有指针在使用前都已正确初始化,并避免使用空指针进行读写操作。
  2. 数组边界检查:确保数组访问时不会越界,使用合适的索引范围。
  3. 内存管理:确保内存的分配和释放过程正确无误,避免出现内存泄漏。
  4. 多线程同步:在多线程程序中,确保共享数据的访问是同步的,避免竞争条件。
  5. 使用调试工具:使用调试器和内存检测工具来跟踪程序的内存访问,定位异常出现的位置。

根据异常信息,您可以看到异常发生在ucrtbased.dll,这是与C运行时库相关的库文件。这可能意味着出现异常的原因与您的代码中的某些操作或内存访问有关,而不是直接由您的代码本身引发的。

第三节 指针、结构体、共用体

  1. 指针(pointer)
    1. 指针就是地址。存放地址的变量就是指针变量。int *p=&a
    2. 数组名为指针常量,可用指针变量访问数组元素。
    int a=10;
    int *p=&a;//p=&a=0x034890234(16进制地址);*p=a=10
    
    int arr[6];
    int *p=arr;
    for(int i=0;i<6;i++)
    {
        printf("a[i]=%d\n",*p);
        p++;
    //或:printf("a[i]=%d\n",p[i]);
    }
    
    1. 指针数组:int *ptr[MAX];
    2. 指向指针的指针:int **p2=&p;
    3. 指针与函数
      1. 指针作为函数参数:(int *p)
      2. 从函数返回指针:C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为static变量。
      3. 函数指针:指向函数的指针int (* p)(int, int) = & max; // &可以省略,调用d = p(p(a, b), c); 等价于d = max(max(a, b), c)
      4. 回调函数:通过函数指针调用的函数。(就是由别人的函数执行时调用你实现的函数。)

案例:d
1.1 回调函数:生成随机数

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

//回调函数
void populate_array(int* array, size_t arraySize, int (*getNextValue)(void))
{
    for (size_t i = 0; i < arraySize; i++)
        array[i] = getNextValue();
}

// 获取随机值
int getNextRandomValue(void)
{
    static int seed = 0;
    if (!seed) {
        seed = (unsigned)time(NULL);//将当前时间作为随机数种子
        srand(seed);
    }
    return rand();
}

int main(void)
{
    int myarray[10];
    /* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
    populate_array(myarray, 10, getNextRandomValue);
    for (int i = 0; i < 10; i++) 
        printf("%d ", myarray[i]);
 
    printf("\n");
    system("pause");
    return 0;
}

1.2 有n个整数,使前面各数顺序向后移动m个位置,最后m个数变成最前面m个数

//有n个整数,使前面各数顺序向后移动m个位置,最后m个数变成最前面m个数
//如:输入:1 2 3 4 5 6 7,向后移动3个位置,输出:5 6 7 1 2 3 4
void move(int array[], int m, int n);

int main(void)
{
    int number[20], n, m;
    printf("how many numbers? ");           //问输入多少数
    scanf_s("%d", &n);

    printf("input %d numbers:\n", n);
    for (int i = 0; i < n; i++)             //输入n个数
        scanf_s("%d", &number[i]);

    printf("how many place you want move? ");//问移动多少个位置
    scanf_s("%d", &m);

    move(number, n, m);                     //调用move移动函数
    printf("now ,they are :\n");
    for (int i = 0; i < n; i++)             //输出
        printf("%d ", number[i]);

    printf("\n");
    system("pause");
    return 0;
}
void move(int array[], int n, int m)
{
    int* p, array_end;                      //需要对原数组进行元素移动,所以使用指针(形参改变实参)
    array_end = *(array + n - 1);           //存储最后一个元素的值
    for (p = array + n - 1; p > array; p--)
        *p = *(p - 1);                      //所有元素后移一位
    *array = array_end;                     //处理最后一个元素
    m--;                                    //记录移动次数(每次移动一位)
    if (m > 0)  move(array, n, m);          //递归调用
}

1.3

  1. 结构体

    1. 结构体:用于用户自定义存储不同类型的数据项。结构体中的数据成员可以是基本数据类型(如 int、float、char 等),也可以是其他结构体类型、指针类型等。
    2. 定义结构:结构体定义由关键字struct和结构体名组成。struct structure_tag {member_list};
    3. 结构体变量初始化
    4. 访问结构体变量
    //结构体
    struct Student//定义
    {
        int ID;
        char name[20];
        int age;
    };
    
    int main(void)
    {   
        //初始化结构体
        struct Student stud1 = { 1001,"anliang",22 };
        printf("ID:%d\nname:%s\nage:%d", stud1.ID, stud1.name, stud1.age);//访问成员
        
        struct Student stud2;
        scanf("%d,%[^,\n],%d", &stud2.ID, stud2.name, &stud2.age);//1002,zhonghui,23
        //scanf("%d%s%d", &stud2.ID, stud2.name, &stud2.age);//1002 zhonghui 23d
        printf("ID:%d\nname:%s\nage:%d", stud2.ID, stud2.name, stud2.age);//呃,写这段的目的是想说明数组名即地址,结果搞成了scanf()如何格式化输入字符串。。。
    
        //结构体指针
        struct Student* point_student = &stud1;  //取地址
        printf("ID:%d\nname:%s\nage:%d", point_student->ID, point_student->name, point_student->age);//指针访问成员
    
        //结构体大小的计算
        printf("结构体 Student 大小为: %zu 字节\n", sizeof(stud1));//28
        printf("\n");system("pause");
        return 0;
    }
    

    注:(1)不能企图通过输出结构体变量名来达到输出结构体变量所有成员的值。
    (2)如果成员本身又属另一个结构体,则要用若干个成员运算符,一层一层地找到最低的一级的成员。
    (3)"."运算符的优先级最高。
    (4)同类结构体变量可以相互赋值。student1=student2;
    (5)对结构体变量成员如同普通变量,可以进行各种运算、引用地址,也可以引用结构体变量的地址。
    另(关于scanf()输入的总结):
    (1)在 Visual Studio (VS) 中,为了加强安全性,默认情况下禁用了 scanf(),以避免潜在的安全问题。如果你希望在 Visual Studio 中使用 scanf(),可以通过以下步骤进行设置:项目->属性->C/C+±>预编译器->预编译定义->末尾添加_CRT_SECURE_NO_WARNINGS->确定保存修改,即可。
    (2)如果使用scanf_s()输入字符串(字符数组),需要添加参数数组的大小,如 scanf_s("%s", str, sizeof(str));
    (3)在输入字符数组时,scanf() 函数会按照指定的格式读取输入,直到遇到空格、换行符或者制表符为止。如果想要’,'来作为分隔符输入字符串,则需要修改scanf()函数的格式化字符串,使用 %[^,\n] 来读取除逗号和换行符以外的所有字符,如scanf("%d,%[^,\n],%d", &stud2.ID, stud2.name, &stud2.age);

    1. 结构体作为函数参数:void printBook( struct Books book );
    2. 结构体指针,使用->访问结构体成员,p->name等价于(*p).name;stu.name;
    3. 结构体大小的计算。注:结构体的大小可能会受到编译器的优化和对齐规则的影响,编译器可能会在结构体中插入一些额外的填充字节以对齐结构体的成员变量,以提高内存访问效率。
    4. 结构体数组struct Student students[10];。指向结构体数组的指针,记住数组名即地址、即指针。

案例:
2.1 编写一个投票程序,三个候选人,先后输入被选人的名字,最后输出各人得票结果

//编写一个投票程序,三个候选人,先后输入被选人的名字,最后输出各人得票结果
//定义候选人结构体
struct Person
{
    char name[20];//姓名
    int count;//得票数
} leader[3] = {"li",0,"zhang",0,"wang",0};//结构体数组,并初始化:三个候选人
//感觉和python里的字典类似

int main(void)
{
    int i, j;
    char leader_name[20];
    printf("input leader_name(10):select(li,zhang,wang)\n");
    for (i = 0; i < 10; i++)
    {
        scanf("%s", leader_name);
        for (j = 0; j < 3; j++)
            if (strcmp(leader_name, leader[j].name) == 0) leader[j].count++;//找到对应候选人姓名,并将票数加1
    }
    printf("result:\n");
    for (i = 0; i < 3; i++)//输出结果
        printf("%-10s%5d\n", leader[i].name, leader[i].count);

    printf("\n");
    system("pause");
    return 0;
}

2.2 有n个结构体变量,内含学生学号、姓名和3门课程的成绩。要求输出平均成绩最高的学生信息(包括学号、姓名、3门课程成绩和平均成绩)

//有n个结构体变量,内含学生学号、姓名和3门课程的成绩。要求输出平均成绩最高的学生信息(包括学号、姓名、3门课程成绩和平均成绩)
 #define N 3         //为方便起见,只定义3个学生 

struct Student      //定义结构体
{
    int id;         //学号
    char name[20];  //姓名
    float score[3]; //3门课程成绩
    float aver;     //平均成绩
};
void input(struct Student stu[]);               //输入学生结构体函数
struct Student max_aver(struct Student stu[]);  //找平均成绩最高的学生函数
void print(struct Student s);                   //输出平均成绩最高的学生信息函数

int main(void)
{
    struct Student stu[N],*p;
    printf("输入%d个学生信息:(id,name,3 score)\n", N);
    p = stu;
    input(p);
    print(max_aver(p));

    printf("\n");
    system("pause");
    return 0;
}
void input(struct Student stu[])
{
    int i, j;
    for (i = 0; i < N; i++)
    {//关于自己写的狗屎代码自己都不想回顾这件事。。。
        scanf("%d %s %f %f %f", &stu[i].id, stu[i].name, &stu[i].score[0], &stu[i].score[1], &stu[i].score[2]);
        for (j = 0, stu[i].aver = 0; j < 3; j++)
            stu[i].aver += stu[i].score[j];
        stu[i].aver /= 3.0;//平均成绩
    }
}
struct Student max_aver(struct Student stu[])
{
    int m = 0;
    for (int i = 1; i < N; i++)
        if (stu[m].aver < stu[i].aver)  m = i;
    return stu[m];
}
void print(struct Student s)
{
    printf("平均成绩最高的学生信息:\n%d %s %6.2f %6.2f %6.2f %6.2f", s.id, s.name, s.score[0], s.score[1], s.score[2], s.aver);
}
  1. 共用体

    1. 共用体是允许在相同的内存位置存储不同的数据类型。可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。
    2. 定义共用体:union union_tag{member_list};
    3. 共用体占用的内存应足够存储共用体中最大的成员。
    4. 访问共用体成员:成员访问运算符.
  2. typedef

    1. 给类型取别名:typedef struct Books Book; Book book;
    2. typedef vs #define
      1. typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,如可以定义 1 为 ONE。
      2. typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。

第四节 链表、文件

  1. 链表(单向链表)
    1. 链表:动态地进行存储分配地一种结构。头指针,结点(数据域、指针域)
    2. 链表必须使用指针变量才能实现,用结构体变量作为结点建立链表。
    3. 指针:head一个结构体的头;next一个结构体的尾,指向下一个结构体的头。
    4. 建立静态链表
    5. 建立动态链表:动态内存分配有关函数malloc,calloc,realloc,free
    6. 单链表的主要操作:链表的建立、插入、删除、输出、查找等。

案例:
1.1 建立静态链表

//链表
struct Student
{
    int id;
    float score;
    struct Student* next;//next是指针变量,指向结构体变量
};

int main(void)
{
    //建立静态链表
    struct Student s1, s2, s3, * head, * p;
    s1.id = 101; s2.id = 102; s3.id = 103;//id赋值
    s1.score = 77; s2.score = 88; s3.score = 99;//score赋值
    head = &s1; s1.next = &s2; s2.next = &s3; s3.next = NULL;//连接结点
    p = head;//通过头指针,遍历链表
    do {
        printf("id:%d\tscore:%6.2f\n", p->id, p->score);
        p = p->next;
    } while (p!=NULL);

    printf("\n");
    system("pause");
    return 0;
}

1.2 建立动态链表

//动态建立链表
struct Student
{
    long num;
    float score;
    struct Student* next;
};
int n;//用于记录学生结点总数

struct Student* creat(void)
{//尾插法
    struct Student* p1, * p2, * head;
    p2 = p1 = (struct Student*)malloc(sizeof(struct Student));//p1,p2先指向同一结点
    head = NULL; n = 0;
    scanf("%ld %f",&p1->num,&p1->score);//输入第一个结点
    while (p1->num != 0)//判断,输入0,0结束
    {
        n = n + 1;//记录结点数
        if (n == 1)
            head = p1;//头结点
        else
            p2->next = p1;//连接第二个及其以后的结点
        p2 = p1;//p2指向最后一个结点
        p1 = (struct Student*)malloc(sizeof(struct Student));//为下一个结点开辟空间
        scanf("%ld %f", &p1->num, &p1->score);
    }
    p2->next = NULL;
    return head;//返回头结点
}

int main(void)
{
    struct Student* pt;
    pt = creat();

    printf("%ld:%6.2f", pt->num, pt->score);

    printf("\n");
    system("pause");
    return 0;
}

1.3 输出链表

void print(struct Student* head)
{
    struct Student* p = head;
    printf("\nNow,these %d records are :\n", n);
    if(head!=NULL)//若不是空表
        do
        {
            printf("%ld:%6.2f\n", p->num, p->score);//输出当前结点
            p = p->next;//p指向下一结点
        } while (p!= NULL);//p不是空地址
}

1.4 链表综合

struct Student
{
	long num;
	float score;
	struct Student* next;
};
int n;//用于记录学生结点总数

//创建链表
struct Student* creat(void)
{//头插法
	struct Student* p1, * head;
	head = NULL; n = 0;
	for (int i = 0; i < 10; i++)
	{
		p1 = (struct Student*)malloc(sizeof(struct Student));
		p1->num = 1000 + i;
		p1->score = 80 + i;
		n = n + 1;//记录结点数
		p1->next = head;
		head = p1;
	}
	return head;//返回头结点
}
//输出链表
void print(struct Student* head)
{
	struct Student* p = head;
	printf("Now,these %d records are :\n", n);
	while (p != NULL)
	{
		printf("%ld:%6.2f\n", p->num, p->score);
		p = p->next;
	}
}
//判断该链表中是否存在某学号学生结点
int exist_stu(struct Student* head, long num)
{
	struct Student* p = head;
	while(p!=NULL)
	{
		if (p->num == num)
			return 1;
		p = p->next;
	}
	return 0;
		
}

//插入结点
struct Student* insert(struct Student* head, long num, struct Student* q)//按学号插入学生
{
	struct Student* p = head;
	struct Student* newHead = head; // 保存新的头结点
	if (exist_stu(head, num))
		n = n + 1;
	else
	{
		printf("没有提供的学号的学生,插入失败!\n");
		return head;
	}

	if (p->num == num) // 如果要插入的位置是头结点
	{
		q->next = p;
		newHead = q; // 更新新的头结点
	}
	else
	{
		while (p != NULL)
		{
			if (p->next->num == num)
			{//插到这个学号的学生前面
				q->next = p->next;
				p->next = q;
				break;
			}
			p = p->next;
		}
	}
	return newHead;
}

//删除结点
struct Student* delete(struct Student* head, long num)//按学号删除结点
{
	struct Student* p = head;
	struct Student* newHead = head; // 保存新的头结点
	if (exist_stu(head, num))
		n = n - 1;
	else
	{
		printf("没有提供的学号的学生,删除失败!\n");
		return head;
	}

	if (p->num == num) // 如果要删除的是头结点
	{
		newHead = p->next; // 更新新的头结点
		free(p);
	}
	else
	{
		while (p != NULL)
		{
			if (p->next != NULL && p->next->num == num)//删除的是p的下一个结点
			{
				struct Student* temp = p->next;
				p->next = p->next->next;
				free(temp);
				break;
			}
			p = p->next;
		}
	}
	return newHead;
}

int main(void)
{
	struct Student* pt;
	pt = creat();//创建链表
	print(pt);//输出链表

	pt = delete(pt, 1001);
	pt = delete(pt, 1010);
	struct Student* p = (struct Student*)malloc(sizeof(struct Student));
	p->num = 2001;
	p->score = 99;
	pt=insert(pt, 1023, p);
	print(pt);

	printf("\n");
	system("pause");
	return 0;
}
  1. 文件
    1. C文件的有关基本知识
      1. 在程序设计中,主要用到两种文件:
        1. 程序文件:源程序文件.c, 目标文件.obj, 可执行文件.exe 等。这些文件内容是程序代码。
        2. 数据文件:文件内容不是代码,而是供程序运行时读写的数据。
      2. 文件一般指存储在外部介质上数据的集合。输入输出流又称数据流,可以分为字符流和字节流。
      3. 文件标识:文件路径、文件名主干、文件后缀。
      4. 文件分类:ASCII文件和二进制文件。
      5. 文件缓冲区:系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区,在数据充满缓冲区后,将缓冲区的数据刷新到磁盘或程序数据区。目的:节省存取时间,提高效率。
      6. 文件类型指针:在C编译环境提供的stdio.h头文件中有文件类型的声明:typedef struct {...} FILE;, 定义FILE *fp;
      7. 文件操作:打开,读/写,关闭。
    2. 打开与关闭文件
      1. 打开文件:FILE *fopen( const char *filename, const char *mode );, fliename(文件名):字符串常量/字符指针/字符数组, mode(使用文件方式)。

        文件使用方式含义如果指定文件不存在
        r(只读)读入数据,打开已存在的文本文件出错
        w(只读)写出数据,打开一个文本文件建立新文件
        a(追加)向文本文件尾添加数据出错
        rb(只读)读入文件,打开一个二进制文件出错
        wb(只写)写出文件,打开一个二进制文件建立新文件
        ab(追加)向二进制文件尾添加数据出错
        r+(读写)读和写,打开一个文本文件出错
        w+(读写)读和写,建立一个新的文本文件建立新文件
        a+(读写)读和写,打开一个文本文件出错
        rb+(读写)读和写,打开一个二进制文件出错
        wb+(读写)读和写,建立一个新的二进制文件建立新文件
        ab+(读写)读写打开一个二进制文件出错

        总结(12种):默认文本文件,b二进制文件,r读,w写,a追加。

      2. 关闭文件:int fclose( FILE *fp );, 返回0成功关闭,否则返回EOF(-1).

      FILE* fp;
      //char filename[] = "F:\\C++project\\文件操作\\test.txt";
      char* filename = "F:\\C++project\\文件操作\\test.txt";
      //打开文件
      fp = fopen(filename, "r");
      if (fp)//#define NULL 0 
      {
          printf("Successfully opened\n");
      }
      
      //关闭文件
      int status=fclose(fp);//返回0成功关闭,否则返回EOF(-1)
      printf("%d", status);
      
    3. 顺序读写数据文件–(文件、文件指针、临时存储文件数据的变量、打开文件、读写文件、关闭文件)
      1. 读写字符:int fgetc( FILE * fp ); int fputc( int c, FILE *fp );
      2. 读写字符串:char *fgets( char *buf, int n, FILE *fp ); int fputs( const char *s, FILE *fp );。fgets和fputs这两个函数的功能类似于gets和puts函数,只是gets和puts函数以终端为读写对象,而fgets和fputs以指定的文件作为读写对象。注:判断是否读到文件尾:(!feof(fp))|((c=fgetc(fp))!=EOF)|(fgets(str[i], 10, fp) != NULL)
      3. 用格式化的方式读写文本文件: fprintf(fp,"%d,%6.2f",i,f);fscanf(fp,"%d,%f",&i,&f);
      4. 用二进制的方式读写数据: fread(buffer,size,count,fp);fwrite(buffer,size,count,fp);,用于存储块的读写,通常是数组或结构体。buffer:数据存储区的地址(如数组、结构体), size:要读写的字节数, count:要读写的数据项数, fp:FILE类型指针。
    4. 随机读写数据文件
      1. 文件位置标记及其定位
        1. 用rewind函数使文件位置标记指向文件开头:rewind(fp)
        2. 用fseek函数改变文件位置标记: fseek(fp,offset,whence);
          1. fseek(fp,100L,0);将文件位置标记向前移到离文件开头100个字节处。在C语言中,L表示一个长整型数值的后缀。它告诉编译器将相应的数值解释为长整型。在这种情况下,100L表示一个长整型数值100。
          2. fseek(fp,50L,1);将文件位置标记向前移到离当前位置50个字节处
          3. fseek(fp,-10L,2);将文件位置标记从文件末尾处向后退10个字节
        3. 用ftell函数测定(得到)文件位置标记的当前位置。
    5. 文件读写的出错检测
      1. ferror函数: ferror(fp)返回0表示未出错,返回非0表示出错。
      2. clearerr函数: 使文件出错标志和文件结束标志置为0;

案例:
2.1 从键盘输入字符,写出到文件,直到用户输入"#"为止

//从键盘输入字符,写出到文件,直到用户输入"#"为止
int main(void)
{
	FILE* fp;
	//char filename[] = "F:\\C++project\\文件操作\\test.txt";
	char* filename = "F:\\C++project\\文件操作\\test.txt";
	
	//打开文件
	fp = fopen(filename, "a");
	if (!fp)
	{
		printf("cannot open file\n");//文件打开失败
		exit(0);//终止程序
	}
	
	//写字符
	printf("向文件写入字符串(以#结束):\n");
	char ch;
	while ((ch = getchar()) != '#')
	{
		fputc(ch, fp);//写入文件
		putchar(ch);
	}

	//关闭文件
	int status=fclose(fp);//返回0成功关闭,否则返回EOF(-1)
	if(status==0) printf("\n写入完毕,文件关闭成功!");

	putchar(10);
	//printf("\n");
	system("pause");
	return 0;
}

2.2 将一个磁盘文件中的信息复制到另一个磁盘文件中(读写字符的方式)

int main(void)
{
	//将一个磁盘文件中的信息复制到另一个磁盘文件中
	FILE* in, * out; char c;
	char infile[50], outfile[50];
	printf("请输入读入文件名和写出文件名:\n");
	scanf("%s%s", infile, outfile);
	
	//打开文件
	in = fopen(infile, "r");
	out = fopen(outfile, "w");
	if (!in)
	{
		printf("读入文件打开失败");
		exit(0);
	}
	if (!out)
	{
		printf("写出文件打开失败");
		exit(0);
	}
	
	//读写操作
	//while ((c = fgetc(in)) != EOF)//判断读取的字符是否已到文件尾(end of file)
	while (!feof(in))//判断文件是否结束,结束feof则返回1
	{
		c = fgetc(in);//读一个字符
		fputc(c, out);//写入一个字符
		putchar(c);//屏幕输出
	}

	//关闭文件
	fclose(in);
	fclose(out);

	putchar(10);
	system("pause");
	return 0;
}

2.3 从键盘读入若干字符串,按字母大小排序,然后写入文件

//从键盘读入若干字符串,按字母大小排序,然后写入文件
int main(void)
{
	//参数
	char str_list[5][10],temp[10],n=5;
	FILE* fp = fopen("F:\\C++project\\文件操作\\out.txt", "w");

	//读字符串--gets
	printf("请输入%d个字符串\n", n);
	for (int i = 0; i < n; i++)
		gets(str_list[i]);

	//排序--选择排序法
	for(int i=0;i<n-1;i++)
	{
		int min = i, j;
		for (j = i + 1; j < n; j++)
			if (strcmp(str_list[min], str_list[j])>0) min = j;//升序,记录最小下标
		if (min != i) //最小的放在i的位置上
		{ //交换
			strcpy(temp, str_list[min]); 
			strcpy(str_list[min], str_list[i]);
			strcpy(str_list[i], temp);
		}
	}

	//写入文件
	if (!fp)
	{
		printf("cannot open file!\n");
		exit(0);
	}
	printf("the new sequence :\n");
	for (int i = 0; i < n; i++)
	{
		fputs(str_list[i], fp);
		fputs("\n", fp);
		puts(str_list[i]);
	}

	putchar(10);
	system("pause");
	return 0;
}

2.4 读回字符串

char str[5][10];
FILE* fp = fopen("F:\\C++project\\文件操作\\out.txt", "r");
if (!fp)
{
    printf("cannot open file!\n");
    exit(0);
}
for (int i = 0; fgets(str[i], 10, fp) != NULL; i++)
{
    printf("%s", str[i]);
}
fclose(fp);

2.5 从键盘输入5个学生的有关数据,然后把它们转存到磁盘文件上去

//从键盘输入5个学生的有关数据,然后把它们转存到磁盘文件上去
 #define N 5
//学生结构体数组
struct Student
{
	int id;
	char name[20];
	int age;
	char addr[15];
};
//存储函数
void save(struct Student stud[])
{
	FILE* fp = fopen("F:\\C++project\\文件操作\\out.txt", "wb");
	if (!fp)
	{
		printf("cannot open file!\n");
		exit(0);
	}
	for (int i = 0; i < N; i++)
	{
		if (fwrite(&stud[i], sizeof(struct Student), 1, fp) != 1)//将数组写入文件
			printf("file write error!\n");
	}

	fclose(fp);
	printf("The write is complete\n");
}
int main(void)
{
	struct Student stud[N];
	printf("Please enter data of students:\n");
	for (int i = 0; i < N; i++)
		scanf("%d %s %d %s", &stud[i].id, stud[i].name, &stud[i].age, stud[i].addr);//存到数组中
	save(stud);

	putchar(10);
	system("pause");
	return 0;
}

2.6 再把上面的5个学生数据读出来

int main(void)
{
	struct Student stud[N];
	struct Student* fp = fopen("F:\\C++project\\文件操作\\out.txt", "rb");
	if (!fp)
	{
		printf("cannot open file!\n");
		exit(0);
	}
	for (int i = 0; i < N; i++)
	{
		fread(&stud[i], sizeof(struct Student), 1, fp);//读文件
		printf("%-4d %10s %4d %10s\n", stud[i].id, stud[i].name, stud[i].age, stud[i].addr);//输出到屏幕
	}

	system("pause");
	return 0;
}

2.7 随机读写–读奇数项学生信息

 #define N 5
//学生结构体
struct Student
{
	int id;
	char name[10];
	int age;
	char addr[15];
};
//二进制写入学生数组
void write_stud(struct Student stud[], char filename[])
{
	//打开文件
	FILE* fp = fopen(filename, "wb");
	if (!fp)
	{
		printf("cannot open file!\n");
		return;
	}
	printf("input %d students info(id,name,age,addr):\n", N);
	for (int i = 0; i < N; i++)
	{//输入一个写一个
		scanf("%d %s %d %s", &stud[i].id, stud[i].name, &stud[i].age, stud[i].addr);
		if (fwrite(&stud[i], sizeof(struct Student), 1, fp) != 1)
			printf("file write error\n");
	}
	//关闭文件
	fclose(fp);
}
//随机读写--读奇数项学生信息
void read_stud(char filename[], struct Student stud[])
{
	//打开文件
	FILE* fp = fopen(filename, "rb");
	if (!fp)
	{
		printf("cannot open file!\n");
		return;
	}
	printf("output students info:\n");
	for (int i = 0; i < 5; i += 2)
	{
		fseek(fp, i * sizeof(struct Student), 0);
		fread(&stud[i], sizeof(struct Student), 1, fp);
		printf("%-4d %10s %4d %10s\n", stud[i].id, stud[i].name, stud[i].age, stud[i].addr);//输出到屏幕
	}
}
int main(void)
{
	struct Student stud[N];
	char filename[] = "F:\\C++project\\文件操作\\out.txt";
	//写
	write_stud(stud, filename);
	//清空结构体数组
	memset(stud, 0, sizeof(stud)); // 将结构体数组的所有字节清零
	//读
	read_stud(filename, stud);

	system("pause");
	return 0;
}

第五节 头文件、源文件等知识补充

  1. C预处理器
    1. C预处理器不是编译器的组成部分,是在实际编译之间完成的预处理,把 C 预处理器(C Preprocessor)简写为 CPP。为了增强可读性,预处理器指令应从第一列开始。

    2. 所有的预处理器命令都是以井号(#)开头。

      指令
      描述
      #define定义宏
      #include包含一个源代码文件
      #undef取消已定义的宏
      #ifdef如果宏已经定义,则返回真
      #ifndef如果宏没有定义,则返回真
      #if如果给定条件为真,则编译下面代码
      #else#if 的替代方案
      #elif如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
      #endif结束一个 #if……#else 条件编译块
      #error当遇到标准错误时,输出错误消息
      #pragma使用标准化方法,向编译器发布特殊的命令到编译器中
    3. 预处理实例

      /*把所有的 MAX_ARRAY_LENGTH 定义为 20*/ 
      #define MAX_ARRAY_LENGTH 20
      
      /*获取源文件,并添加内容到当前的源文件中*/
      #include <stdio.h>// 从系统库中获取 stdio.h
      #include "myheader.h"//从本地目录中获取 myheader.h
      
      /*这个指令告诉 CPP 只有当 MESSAGE 未定义时,才定义 MESSAGE。*/
      #ifndef MESSAGE
          #define MESSAGE "You wish!"
      #endif
      
      /*这个指令告诉 CPP 如果定义了 DEBUG,则执行处理语句。*/
      #ifdef DEBUG
           /* Your debugging statements here */
      #endif
      
    4. 预定义宏

      #include <stdio.h>
      
      int main()
      {
          printf("File :%s\n", __FILE__);//当前文件名
          printf("Date :%s\n", __DATE__);//当前日期
          printf("Time :%s\n", __TIME__);//当前时间
          printf("Line :%d\n", __LINE__);//当前行号
          printf("ANSI :%d\n", __STDC__);//当编译器以 ANSI 标准编译时,则定义为 1。
          return 0;
      }
      
    5. 预处理器运算符

      1. 宏延续运算符(\)
      2. 字符串常量化运算符(#)
      3. 标记粘贴运算符(##)
      4. defined() 运算符
    6. 参数化的宏: 模拟函数。如#define square(x) ((x) * (x))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

物往fd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值