C语言基础

一、选择控制语句

1、if语句
if(条件表达式) //条件成立,执行下方内容,不成立就跳过
{
       执行内容;
}
if(条件表达式)
{
    执行内容;
}
//条件成立,执行if内的内容,不成立,跳过if执行else内的内容
else
{
    执行内容;
}
//多个条件判断用else if,最后以else收尾
if(条件表达式)
{
    执行内容;
}
else if(条件表达式)
{
    执行内容;
}

else if(条件表达式)
{
     执行内容;
}
    .
    .
    .
else
{
    执行内容;
}
2、switch语句
switch(表达式)  //表达式只能是字符型或者整形
{
    case 常量表达式1:
        语句1;
    break;   //跳出switch

    case 常量表达式2:
        语句2;
    break;

    default:
    语句3;
    break;

}
//case后的常量表达式与switch后的表达式进行比较
//如果相等则执行下方语句,执行完break跳出
//以上条件都不符合执行defaul内的语句然后跳出
//在实际应用中根据需要调整,也可以直接跳出
3、for循环
for(表达式1;表达式2;表达式3)
{
    "循环体";
}
//表达式1整个循环过程只执行一次
//表达式2为循环条件,每做一次循环就回头和2比较,为真继续循环,为假跳出循环
//表达式3在表达式2为真后执行

例:

int i=0;
for(i=1;i<=10;i++)
{
    "循环体";
}
//过程:
//(1)i初始值为1;
//(2)i<=10成立;
//(3)执行i++,此时i=2;
//(4)执行循环体
//(5)i<=10成立;
//(6)执行i++,此时i=3;
//        .
//        .
//        .
//当i<=10为假时跳出循环
4、while循环
while(条件表达式)
{
    "循环体";
}
//执行while循环先判断while后的表达式是否为为真
//与for循环的区别就是for循环先执行再判断
//while循环是先判断再执行
do
{
    "循环体";
}while(条件表达式);

//do while循环先执行do后的循环体,在进行判断
5、goto语句

goto是跳转语句,只能在同一函数内部跳转,无法跨函数跳转

下面是我在学习过程中遇到的问题(红色字体)

当 goto 语句向后跳转时,在 goto 到的板块执行完后,程序不会回头执行 goto 语句和目标标签之间的代码块,这些代码块会被直接跳过,程序会按照正常的顺序继续执行目标标签之后的代码。


#include <stdio.h>
void func() {
    int i = 0;
   goto start;//跳到start板块
    printf("这行代码不会被执行\n");
start:
    while (i < 3) {
        printf("i = %d\n", i);
        i++;
        if (i == 2) {
            // 向前跳转
            goto end;
        }
    }
end:
    printf("循环结束\n");
}

int main() {
    func();
    return 0;
}

二、数组

1、数组分类

按数据类型分类

//字符型
char s[10];        s[0]、s[1]、s[2]、...s[9]

//短整型

short int a[10] ;        a[0]、a[1]、a[2]=7、...a[9]=8

//整形

int a[10];        a[0]、a[1]、a[2]、...a[9]

//长整型

long int a[10];       a[0]、 a[1]、a[2]、...a[9]

//浮点型

float a[10];       a[0]、 a[1]、a[2]、... a[9]=3.14f

double a[6];        a[0]、 a[1]、a[2]、...a[9]=3.1415926

//指针数组

char *a[8];

int *a[8];

//结构体数组

struct stu boy[10];

按维数分类

//一维数组
int a[10];
a[0]、a[1]、a[2]、a[3]、a[4]、a[5]、a[6]、a[7]、a[8]、a[9]

//二维数组
int a[2][3];
a[0][0]、a[0][1]、a[0][2]
a[1][0]、a[1][1]、a[1][2]

//多维数组
int a[2][3][4];    //可以看成是多个二维数组构成的
2、定义一个数组

定义一维数组

//定义了一个名字为a,含有5个元素的数组
int a[5];


//定义数组时不给出数组元素的个数,通过初始化定义数组大小
int a[];
a[]={1,2,3,4,5};    //a[0]=1、a[1]=2、a[2]=3、a[3]=4、a[4]=5

定义二维数组

//前一个表示行数,后一个表示列数
int a[2][3];    //定义了2行3列的数组,即
                //a[0][0]、a[0][1]、a[0][2]
                //a[1][0]、a[1][1]、a[1][2]


//二维数组在定义时可不给出行数,但必须有列数
int a[][3];
a[][3]={1,2,3},
       {4,5,6};    //通过初始化定义了行数
3、数组的初始化

一维数组的初始化

//全部初始化
int a[5]={1、2、3、4、5};

//部分初始化
int a[5]={1、2、3};    //位数不够的后面补0
                        //即a[0]=1、a[1]=2、a[2]=3、a[3]=0、a[4]=0

二维数组的初始化

//全部初始化
int a[2][3]={{1,2,3},{3,4,5}};

//部分初始化
int a[2][3]={{1,2,5},{3,4}}; //同理,后面0补齐

//逐个初始化
a[2][3]={1,2,3,4,5,6}; //逐个初始化顺序是先从第零行开始赋值
                       //然后下一行
4、数组的引用(遍历数组)

字符数组以"\0"结尾,比实际多一位

//遍历一维数组
#include <stdio.h>

int main() {
    // 定义一个包含 5 个元素的整型数组
    int arr[5] = {1, 2, 3, 4, 5};
    int i;

    // 使用 for 循环遍历数组
    for (i = 0; i < 5; i++) {
        // 打印数组中每个元素的值
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    return 0;
}



//遍历字符数组
#include <stdio.h>

int main() {
    // 定义一个字符数组(字符串)
    char str[] = "Hello";
    int i;

    // 遍历字符数组
    for (i = 0; str[i] != '\0'; i++) {
        // 打印字符数组中每个字符
        printf("%c ", str[i]);
    }
    printf("\n");

    return 0;
}



//遍历多维数组
#include <stdio.h>

int main() {
    // 定义一个 2 行 3 列的二维整型数组
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int i, j;

    // 外层循环遍历行
    for (i = 0; i < 2; i++) {
        // 内层循环遍历列
        for (j = 0; j < 3; j++) {
            // 打印二维数组中每个元素的值
            printf("arr[%d][%d] = %d\n", i, j, arr[i][j]);
        }
    }

    return 0;
}

三、函数

1、函数的分类

(1)C语言库函数

#include <stdio.h>    //输入输出函数

这类函数用于处理输入和输出操作,比如从标准输入读取数据,向标准输出或文件写入数据等。

#include <stdio.h>    //输入输出函数



printf:格式化输出到标准输出设备(通常是屏幕)。
#include <stdio.h>

int main() {
    int num = 10;
    printf("The number is: %d\n", num);
    return 0;
}



scanf:从标准输入设备(通常是键盘)读取格式化输入。
#include <stdio.h>

int main() {
    int num;
    printf("Enter a number: ");
    scanf("%d", &num);
    printf("You entered: %d\n", num);
    return 0;
}



fopen:打开一个文件,返回一个文件指针。
#include <stdio.h>

int main() {
    FILE *fp = fopen("test.txt", "w");
    if (fp != NULL) {
        fputs("Hello, World!", fp);
        fclose(fp);
    }
    return 0;
}

  #include <string.h>    //字符串处理函数 

  这些函数用于处理字符串,包括字符串的复制、连接、比较等操作。

strcpy:将一个字符串复制到另一个字符串。
#include <stdio.h>
#include <string.h>

int main() {
    char src[] = "Hello";
    char dest[10];
    strcpy(dest, src);
    printf("Copied string: %s\n", dest);
    return 0;
}




strcat:将一个字符串连接到另一个字符串的末尾。
#include <stdio.h>
#include <string.h>

int main() {
    char str1[20] = "Hello";
    char str2[] = " World";
    strcat(str1, str2);
    printf("Concatenated string: %s\n", str1);
    return 0;
}




strcmp:比较两个字符串的大小。
#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "apple";
    char str2[] = "banana";
    int result = strcmp(str1, str2);
    if (result < 0) {
        printf("%s is less than %s\n", str1, str2);
    } else if (result > 0) {
        printf("%s is greater than %s\n", str1, str2);
    } else {
        printf("%s is equal to %s\n", str1, str2);
    }
    return 0;
}

  #include<math.h>        //数学函数

  用于执行各种数学运算,如三角函数、对数函数、幂函数等。

sqrt:计算一个数的平方根。
#include <stdio.h>
#include <math.h>

int main() {
    double num = 25.0;
    double result = sqrt(num);
    printf("Square root of %lf is %lf\n", num, result);
    return 0;
}




sin:计算一个角度的正弦值(角度以弧度为单位)。
#include <stdio.h>
#include <math.h>

int main() {
    double angle = 30.0;
    double rad = angle * M_PI / 180.0;
    double result = sin(rad);
    printf("Sine of %lf degrees is %lf\n", angle, result);
    return 0;
}

  #include<stdlib.h>        //内存管理函数

  用于动态分配和释放内存。

malloc:分配指定大小的内存块。
free:释放之前由malloc、calloc或realloc分配的内存块。
#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr = (int *)malloc(5 * sizeof(int));
    if (ptr != NULL) {
        for (int i = 0; i < 5; i++) {
            ptr[i] = i;
        }
        for (int i = 0; i < 5; i++) {
            printf("%d ", ptr[i]);
        }
        free(ptr);
    }
    return 0;
}

  #include<time.h>        //时间日期函数

  用于处理时间和日期相关的操作。

time:返回当前的时间(以秒为单位,从 1970 年 1 月 1 日开始计算)。
#include <stdio.h>
#include <time.h>

int main() {
    time_t current_time = time(NULL);
    printf("Current time in seconds since epoch: %ld\n", current_time);
    return 0;
}

  #include<ctype.h>        //字符处理函数

  用于对单个字符进行处理和判断。

isupper:判断一个字符是否为大写字母。
#include <stdio.h>
#include <ctype.h>

int main() {
    char ch = 'A';
    if (isupper(ch)) {
        printf("%c is an uppercase letter.\n", ch);
    } else {
        printf("%c is not an uppercase letter.\n", ch);
    }
    return 0;
}

(2)有参和无参函数

  有参函数

//参数在后面的()内,可以是一个或多个,参数类型随意
int function(int a,float b,double c)
{
    ...;
}

  无参函数

//无参函数()内什么都不写或者写入void
int function(void)
{
    ...;
}

int function()
{
    ...;
}

(3)有无返回值的函数

 有返回值的函数

//函数体内必须有return
//返回值的类型在函数
int function() //表示返回int值
{
    ...;
    return 1;
}

//若没有给出返回值类型,默认为int型
function()
{
    ...;
    return 0;
}

  无返回值的函数

//在定义函数时前面加void
void function(形参表)
{
    ...;
    return;
}
2、调用函数

变量名=函数名(实参列表)  //带返回值

函数名(实参列表)        //不带返回值

//有返回值
int function(int x,int y)
{
    ...;
}
int main()
{
    int number;
    number=function(4,8);
        .
        .
        .
}


//无返回值
void function()
{
    printf("hello,world");
}
int main()
{
    function();
}

四、变量的储存类型

1、内存分区简介

(1)内存分为物理内存和虚拟内存我们写程序时看到的都是虚拟内存

 操作系统会在物理内存和虚拟内存之间做映射

(2)在运行程序时系统将虚拟内存分区

 

 动态申请内存时,在堆里开辟空间   

 

 主要存放局部变量   

 静态全局区

 代码区 

 存放我们的代码

 文字常量区

2、普通全局变量

概念:在函数外部定义的变量

int a=100;    //a为全局变量
int main()
{
    .
    .
    .
}

作用范围:程序的所有地方,用之前声明 extern int a;

生命周期:程序运行的整个过程,从开始到结束

注意:如果定义一个全局变量不付初值,默认初值为0

3、静态全局变量 static
static int a;    //a是一个静态全局变量
int main()
{
    .
    .
    .
}

与全局变量的区别就是加了static只能在它所属的.c文件中使用,无法使用extern,其他的一样

4、普通局部变量

概念:函数内部定义的或者复合语句中定义的

int main()
{
    int a;    //局部变量
    for()
    {   
        int b;    //局部变量
    }
}

 作用范围:只在函数内部有效,只在复合语句内部有效

 生命周期:函数调用前不占用空间,调用函数时才开辟空间给局部变量,函数结束空间释放

 5、静态局部变量

 与局部变量的区别在生命周期

 第一次调用函数开辟空间赋值,函数结束时不释放此后再次调用该函数不再开辟空间,也不赋初值,用的还是static的变量

6、外部函数和内部函数

外部函数:我们定义的函数都是外部函数,即可在程序的任何一个.c文件中使用

内部函数:加static,只能在此.c文件中使用

五、预处理、动态库、静态库

1、C语言编译过程

(1)预编译

       将.c的头文件展开,宏展开

       生成的文件是.i文件

(2)编译

       将预处理后的.i文件生成.s文件

(3)汇编

       将.s文件生成.o文件

(4)链接

       将.o文件链接成目标文件

2、define宏定义

(1)不带参宏

定义宏用define去定义

终止宏定义用undef

#define PI 3.14    //预编译时出现PI就用3.14去替换
                   //想要终止宏定义用#undef
int main()
{
    double a;
    a=PI;    //此时a=3.14
    #undef PI    //终止宏定义PI=3.14
    #define PI 6.28
    double b;
    b=PI;    //此时PI=6.28
    return 0;
}

(2)带参宏

#define S(a,b) a*b        //例如遇到S(2,6)替换成2*6

注意:a、b不带类型名

3、选择性编译

(1)#ifdef

//如果在当前.c的ifdef的上边定义过了BBB,就编译代码段1
//否则编译代码段2
#ifdef BBB
    代码段1;
#else
    代码段2;
#endif
#include <stdio.h>

// 定义一个宏
#define DEBUG

int main() {
    // 使用 #ifdef 检查 DEBUG 宏是否被定义
    #ifdef DEBUG
        printf("调试模式已开启,正在输出调试信息...\n");
    #endif

    printf("程序正常运行...\n");

    return 0;
}

//与if else语句最直接的区别就是if else的条件是表达式,ifdef是条件

(2)#ifndef 

与ifdef互补

#ifndef BBB
    代码段1;
#else
    代码段2;
#endif

(3)#if

//表达式为真,执行第一段代码,否则执行第二段
#if 表达式
    程序段1;
#else
    程序段2;
#endif
4、静态库与动态库

(1)静态编译使用的是静态库文件进行编译,动态编译用的是动态库文件进行编译

(2)二者区别:

        a、使用的库文件格式不一样

        b、静态编译把静态库文件打包编译到可执行程序中

        c、动态编译不会把动态库文件打包编译到可执行程序中,只是编译链接关系

5、静态库与动态库文件的制作

静态库文件的制作

静态库在链接时会被完整地复制到可执行文件中,程序运行时不再依赖静态库文件。以下是制作静态库的详细步骤:

(1) 编写源文件

假设我们有两个源文件 add.c 和 sub.c,分别实现加法和减法功能,同时有对应的头文件 math.h 声明函数。

#include "math.h"

int add(int a, int b) {
    return a + b;
}
#include "math.h"

int sub(int a, int b) {
    return a - b;
}
#ifndef MATH_H
#define MATH_H

int add(int a, int b);
int sub(int a, int b);

#endif

(2) 编译源文件生成目标文件

使用 gcc 编译器将源文件编译成目标文件(.o):

gcc -c add.c sub.c

这会生成 add.o 和 sub.o 两个目标文件。

(3)创建静态库

使用 ar 工具将目标文件打包成静态库文件:

ar rcs libmath.a add.o sub.o
  • r:将目标文件插入到静态库中。
  • c:创建静态库,如果库文件不存在则创建。
  • s:写入一个目标文件索引到库中,或者更新一个已存在的索引。

(4)使用静态库

编写一个测试程序 test.c

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

int main() {
    int a = 10, b = 5;
    printf("Addition: %d\n", add(a, b));
    printf("Subtraction: %d\n", sub(a, b));
    return 0;
}

编译并链接静态库:

gcc -o test test.c -L. -lmath

-L.:指定库文件的搜索路径为当前目录。

-lmath:链接名为 libmath.a 的静态库。

(5)运行程序

./test

动态库文件的制作

动态库在程序运行时才会被加载,多个程序可以共享同一个动态库,节省内存空间。以下是制作动态库的详细步骤:

(1)编写源文件

使用上面的 add.csub.c 和 math.h 文件。

(2) 编译源文件生成目标文件

使用 gcc 编译器,并添加 -fPIC 选项,生成与位置无关的代码:

gcc -fPIC -c add.c sub.c

-fPIC:生成位置无关代码,使得动态库可以在不同的内存地址加载。

(3)创建动态库

使用 gcc 编译器将目标文件链接成动态库文件:

gcc -shared -o libmath.so add.o sub.o

-shared:指定生成动态库文件。

(4)使用动态库

编写测试程序 test.c(与静态库测试程序相同):

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

int main() {
    int a = 10, b = 5;
    printf("Addition: %d\n", add(a, b));
    printf("Subtraction: %d\n", sub(a, b));
    return 0;
}

编译并链接动态库:

gcc -o test test.c -L. -lmath

(5)运行程序

在运行程序之前,需要将动态库的路径添加到系统的动态库搜索路径中。可以通过设置 LD_LIBRARY_PATH 环境变量来实现:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
./test

六、指针

1、指针的定义方法

(1)简单的指针变量

数据类型 * 指针变量名

int* p;    //*是用来修饰变量的,说明p是一个指针变量
           //变量名是p

(2)指针的运算符

“ & ”取地址、“ * ”取值

int a=53;
int *p;
p=&a;
//p保存了a的地址,也可以说p指向了a;

int num;
num=*p;
//此时,num=a;
2、指针和变量的关系

(1)指针的类型:int、long int、float、函数指针、结构体指针等

 对应类型的指针只能保存对应类型数据的地址,如果想让不同类型的变量相互赋值,需要强制数据类型转换

//强制转换数据类型
int a=33;
char*p;
p=(char*)&a;

(2)变量地址

        在 C 语言里,每个变量在内存中都有一个起始地址,这个起始地址指向该变量所占内存块的第一个字节。对于一个占四个字节的变量 a,它的地址就是这四个连续字节中第一个字节的地址。

比如有一个int型变量a占有四个字节,每一个字节都有一个地址,那么这个a变量的指针指向第一个字节的地址。

#include <stdio.h>

int main() {
    int a = 10;  // int 类型通常占四个字节
    char *p = (char *)&a;  // 将 int 类型变量 a 的地址强制转换为 char 类型指针

    // 输出变量 a 的起始地址
    printf("变量 a 的起始地址: %p\n", (void *)&a);

    // 输出 a 所占四个字节的每个字节的地址
    for (int i = 0; i < 4; i++) {
        printf("第 %d 个字节的地址: %p\n", i + 1, (void *)(p + i));
    }

    return 0;
}

//1.定义变量:
//int a = 10; 定义了一个 int 类型的变量 a,
//在大多数系统中,int 类型占四个字节。
//2.指针类型转换:
//char *p = (char *)&a; 将 a 的地址强制转换为 char 类型的指针。
//char 类型占一个字节,通过 p + i 可以依次访问 a 所占四个字节中的每个字节。
//3.输出地址:
//printf("变量 a 的起始地址: %p\n", (void *)&a); 输出变量 a 的起始地址。
//通过 for 循环,printf("第 %d 个字节的地址: %p\n", i + 1, (void *)(p + i));
//依次输出 a 所占四个字节中每个字节的地址。

        为什么要强行将int型变量a转换成char型指针?

(1)指针步长的概念

在 C 语言中,指针的步长取决于它所指向的数据类型。当对指针进行算术运算(如 p + 1)时,指针会根据其指向的数据类型的大小向前移动相应的字节数。具体规则如下:

对于 char 类型指针,其指向的数据类型是 charchar 类型通常占 1 个字节,所以 char 类型指针每次加 1,指针会向前移动 1 个字节。

对于 int 类型指针,其指向的数据类型是 int,在大多数系统中 int 类型占 4 个字节,所以 int 类型指针每次加 1,指针会向前移动 4 个字节。

示例:

#include <stdio.h>

int main() {
    int a = 10;
    int *int_ptr = &a;
    char *char_ptr = (char *)&a;

    // 输出 int 类型指针加1后的地址变化
    printf("int 类型指针初始地址: %p\n", (void *)int_ptr);
    printf("int 类型指针加1后的地址: %p\n", (void *)(int_ptr + 1));

    // 输出 char 类型指针加1后的地址变化
    printf("char 类型指针初始地址: %p\n", (void *)char_ptr);
    printf("char 类型指针加1后的地址: %p\n", (void *)(char_ptr + 1));

    return 0;
}

在上述代码中:

int_ptr 是 int 类型指针,int_ptr + 1 会使指针向前移动 4 个字节(因为 int 类型通常占 4 个字节)。

char_ptr 是 char 类型指针,char_ptr + 1 会使指针向前移动 1 个字节(因为 char 类型占 1 个字节)。

(2)逐个字节访问内存的需求

如果想要逐个字节地访问 int 类型变量 a 所占用的 4 个字节的内存,就需要使用 char 类型指针。因为 char 类型指针每次加 1 只会移动 1 个字节,这样就可以依次访问到 a 所占的每个字节。例如:

#include <stdio.h>

int main() {
    int a = 10;
    char *p = (char *)&a;

    for (int i = 0; i < 4; i++) {
        printf("第 %d 个字节的地址: %p\n", i + 1, (void *)(p + i));
    }

    return 0;
}

在这个代码中,通过将 int 类型变量 a 的地址强制转换为 char 类型指针,就可以使用 p + i 依次访问 a 所占的 4 个字节的内存地址。

综上所述,将 int 类型变量的地址强制转换为 char 类型指针是为了利用 char 类型指针的步长特性,实现对 int 类型变量所占用内存空间的逐个字节访问。

3、指针和数组元素的关系

(1)示例:

int a[5];
int *p;
p=&a[0];
//p中保存了数组a中的a[0]的地址

(2)数组元素的引用方法

   方法一:直接引用

//方法1
int a[5];
a[2]=100;

   方法二:指针名+下标 

//方法2
int a[5];
int *p;
p=a;    
p[2]=100;

   方法2中的p=a可能有人看不懂,p不是指针变量吗?怎么能被a赋值呢?

  在C 语言中,数组名在大多数表达式里会被隐式转换为指向数组首元素的指针,也就是说,数组名代表的是数组首元素的地址

  方法三: 通过指针变量运算加取值

//方法3
int a[5];
int *p;
p=a;
*(p+2)=100;    //相当于a[2]=100;

  方法四:通过数组名+取值的方法 

//方法4
int a[5];
*(a+2)=100;//相当于a[2]=100;

  对于方法四的解释上面说过了,数组名代表的是数组首元素的地址

4、指针的运算

(1)指针可以加一个整数,往下指几个它指向的变量,结果还是地址

  指针指向数组的时候,加上一个整数才有意义

int a[5];
int *p;
p=a;
p+2;    //p是a[0]的地址,p+2是a[2]是&a[2];

  例如p保存的地址编号是2000,则p+2代表的地址编号是2008;

char num[5];
char *p;
p=num;
p+2;    //相当于&num[2]

  例如p存放的地址是2000,则p+2代表的地址是2002;

  取决于变量的类型占几个字节;

(2)两个相同类型的指针可以比较大小

  前提:只有两个相同类型的指针指向同一个数组的元素时,比较大小才有意义,指向前一个元素地址的指针小于指向后一个元素的指针

#include<stdio.h>
int main(int argc,char *argv[])
{
    int a[10];
    int *p,*q,n;
    p=&a[1];
    q=&a[6];
    if(p<q)
    {
        printf("p<q\n");
    }
    else if(p>q)
    {
        printf("p>q\n");
    }
    else
    {
        printf("p=q\n");
    }
    return 0;
}

        运行结果:

p<q

(3)两个相同类型的指针可以做减法

前提:两个相同类型的指针指向同一个数组的元素时,做减法才有意义,做减法的结果是,两个指针指向的中间有多少个元素。这个数组

#include<stdio.h>
int main()
{
    int a[10];
    int *p,*q;
    p=&a[0];
    q=&a[3];
    printf("%d\n",q-p);
}

(4)两个相同类型的指针可以相互赋值

int *p;
int *q;
int a;
p=&a;
q=p;
//不同类型的指针相互赋值必须进行强制类型转换
5、指针数组

(1)指针和数组的关系

  指针可以保存数组元素的地址;

  可以定义一个数组,数组中有若干个相同类型指针变量这个数组被称为指针数组;        

指针数组的概念:

  指针数组本身是一个数组,是若干个相同类型的指针变量构成的集合;

(2)指针数组的定义方法

int *p[5]; //例如我定义了一个指针数组
//用法
int a;
p[0]=&a;

int b[10];
p[1]=&b[5];

其中,p[2]和 *(p+2)是等价的都是指针数组中第二个元素

(3)字符指针数组

#include<stdio.h>
int main()
{
    char *name[5]={"hello","china","beijing","project","computer"};

    int i;

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

    return 0;
}
6、指针的指针

  指针的指针,即指针的地址,我们定义了一个指针变量,它本身占用了四个字节,也有地址编号。

int a=0x12345678;
//假设a的地址为0x00002000;
int *p;
p=&a;
//p中存放的地址编号为0x00002000;
//假设p的地址编号为0x00003000;
int **q;
q=&p;
//q中存放的p的地址为0x00002000;
7、字符串和指针

(1)字符串概念:以“\0”结尾的若干个字符的集合,如“helloworld”;

(2)字符串的地址:第一个字符的地址,如“helloworld”的地址就是“h”的地址,我们可以定义一个字符指针变量保存字符串的地址,如char *s=“helloworld”;

  (3)字符串的储存形式:数组、文字常量区、堆

a.字符串存放在数组中

字符串存放在数组中,其实就是内存中开辟了一段空间放字符串;

char string[100]="helloworld";

b.字符串存放在文字常量区 在文字苍凉去开辟了一段空间存放字符串,将字符串的首地址赋给指针变量;

char *str="helloworld";

定义了一个指针变量str只能存放字符地址编号,对于“helloworld”这个字符串中的字符不是存放在str指针变量中,str只存放了“h”的地址编号,整个“helloworld”存放在文字常量区

c.字符串存放在堆区

 使用malloc等函数在堆区申请空间,将字符串拷贝到堆区;

char *str=(char*)malloc(10*sizeof(char));
//动态申请了10个字节的储存空间
//首地址给str赋值

strcpy(str."helloworld");
//将字符串“helloworld”拷贝到str指向的内存里
8、字符串的可修改性

字符串内容是否可以修改,取决于字符串存放在哪里;

(1)存放在数组中的字符串的内容是可修改的

char str[100]="madeinchina";
str[0]=k;
//则str内存放的字符串变为"kadeinchina";

 (2)文字常量区里的内容是不可修改的

char *str="helloworld";
*str='k';//错误
//“h”存放在文字常量区,不可修改

注:1.str指向文字常量区的时候,它指向的内存的内容不可修改。
    2.str是指针变量可以指向别的地方,即可以给str重新赋值。

 (3)堆区的内容是可以修改的

char *str=(char*)malloc(10);
strcpy(str."helloworld");
*str='y';//正确
9、字符串三种储存形式的初始化及使用赋值

(1)字符数组初始化:

char buf_aver[20]="hello world";

使用时赋值:

  使用scanf或者strcpy

char buf[20]="helloworld";

buf="hellomother";//错误,因为字符数组的名字是常量,本恩用“=”给常量赋值。

strcpy(buf,"hellomother");//正确,数组中的内容是可以修改的
scanf("%s",buf);//正确

(2)指针指向文字常量区,初始化:

char *buf_point="hello world";

 使用时赋值:

char *buf_point="helloworld";

buf_point="hellokitty";//正确。buf_point指向另一个字符串
strcpy(buf_point,"hellokitty");//错误,这种情况,buf_point指向的是文字常量区,内容只读

(3)指针指向堆区,堆区存放字符串

        不能初始化,只能先给指针赋值,让指针指向堆区,再使用strcpy、scanf等方法把字符串拷贝到堆区。

char *buf_heap;
buf_heap=(char*)malloc(15);
strcpy(buf_heap,"hello world");
scanf("%s",buf_heap);

使用时赋值:

char *buf_heap;
buf_heap=(char*)malloc(15);
strcpy(buf_heap,"helloworld");
scanf("%s",buf_heap);
10、数组指针

数组指针是一个指针,指针数组是一个数组

数组指针的定义方法

int (*p)[5];//定义了一个数组指针变量p
//p指向的是整型的有5个元素的数组
//p+1往下指五个整型,跳过一个有五个整型元素的数组
#include<stdio.h>
int main()
{
    int a[3][5];//定义了一个3行5列的一个二维数组
    int(*p)[5];//定义一个数组指针变量p,p+1跳一个有5个元素的整型数组
    printf("a=%p\n",a);//第0行的行地址
    printf("a+1=%p\n",a+1);//第1行的行地址,a和a +1差20个字节
    p=a;
    printf("p=%p\n",p);
    printf("p+1=%p\n",p+1);//p+1跳一个有5个整型元素的一维数组
    return 0;
}

七、动态内存申请

1、动态分配函数

stdlib.h

(1)malloc函数

函数原型:void *malloc(unsigned int size);

功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。函数原型返回void*指针,使用时必须做强制的类型转换,分配的空间内容不确定,一般使用memset初始化。

返回值:分配空间的起始地址(分配成功)

NULL(分配失败)

注意:

a.在调用malloc之后,一定要判断下是否申请内存成功。

b.如果多次malloc申请内存,申请的内存不一定是连续的

使用方法:

char *p;
p=(char*)malloc(20);

(2)free函数(释放内存函数)

函数定义:void free(void *ptr)

函数说明:free函数释放ptr指向的内存,其中,free释放的必须是malloc、calloc、realloc申请的动态内存。

使用方法:

char *p=(char*)malloc(20);
free(p);

注意:free后,因为没有给p赋值,所以p还是指向原先动态申请的内存,但是不能用,因为被释放掉了,此时p成了野指针。

(3)calloc函数

函数定义:void* calloc(size_t nmemb,size_t size);

(size_t实际上是无符号整型,它在头文件中是用typedef定义出来的);

函数功能:在内存的堆中,申请nmemb块,每块的大小为size个字节的连续区域

使用方法:

char*p(char*)calloc(3,100);
//在堆中申请了三块,每块大小100个字节,即300个字节的连续区域;

(4)realloc函数(重新申请内存)

我们调用calloc和malloc函数时,单次申请的内存是连续的,多次不一定连续,如果我们申请了一次内存,我们想再次申请的内存挨着原来申请的内存,或者想释放后面的一部分内存,我们就是用realloc。

函数定义:void* realloc(void* s,unsigned int newsize);

函数功能:在原先s指向的内存上重新申请内存,新的内存大小为newsize个字节,如果原先内存后面有足够大的空间,则追加,如果后面的内存不够用,则realloc函数会在堆区找一个newsize个字节大小的内存申请,然后将原先内存的内容拷贝过来,然后释放原先的内存,最后返回新内存的地址。

如果newsize比原先的内存小,则会释放原先内存的后面的储存空间,只留前面newsize个字节。返回值:新申请内存的首地址;

例:(可以参考以下代码再去回顾上方的解释)

char*p;
p=(char*)malloc(100);
//在100后面追加50个字节
p=(char*)realloc(p,150);
//p指向的新的内存大小为150个字节

例:(第二种情况示例)

char*p;
p=(char*)malloc(100);
//重新申请内存,内存大小为50字节
p=(char*)realloc(p,50);
//p指向的内存的新的大小为50字节,原100字节后面的50字节被释放;

 calloc、malloc、realloc动态申请的内存,只有在free或者程序结束时才被释放;

2、内存泄偶

概念:申请的内存首地址丢失,再也没法使用了,也没法释放,这块内存就泄露了

例1:

int main()
{
    char*p;
    p=(char*)malloc(100);
    //p指向了一块内存;
    p="thank you";//p指向了其他地方
    //因此你无法找到原来申请100内存的首地址,无法使用
    
    
    return 0;
}

例2:

void function()
{
    char*p;
    p=(char*)malloc(100);
}

int main()
{
    function();
    function();
    
    return 0;
}
//每调用一次function,泄露100个字节;

内存泄漏解决方法:

方法一:

void function()
{
    char*p;
    p=(char*)malloc(100);
    
    
    free(p);
}
//函数用完就释放

int main()
{
    function();
    function();
    
    return 0;
}

方法二:

char*function()
{
    char*p;
    p=(char*)malloc(100);
    
    
    return p;
}

int main()
{
    char*q;
    q=function();
    //通过中间变量q来使用动态申请100字节内存;
    
    free(q);
    //用完释放;
    
    return 0;
}

八、字符串处理函数

#pragma指令:用于指定计算机或者操作系统特定的编译功能;

#pragma warning(disable:4996)  在c文件开始处写上这句话,即告诉编译器忽略4996警告,strcpy、scanf等一些不安全的标准c库函数在vs中可以使用。

1、_strlen函数

函数定义:size_t strlen(const char* s);

函数功能:测字符指针s指向的字符串中字符的个数,不包括'\0';

返回值:字符串中字符个数;

例:

#include<stdio.h>
#include<string.h>
int main()
{
    char str1[20]="hello";
    char*str2="hello";


    printf("%d\n",sizeof(str1));//20
    printf("%d\n",sizeof(str2));//4

    printf("%d\n",strlen(str1));//5
    printf("%d\n",strlen(str2));//5

    return 0;
}

sizeof是关键字,测量的数据占用内存空间大小;

如果sizeof测量的是数组的名字,则测得是数组占多少个字节;

如果sizeof测的是指针变量,则测的是指针变量本身占几个字节;

strlen是库函数,它测的是字符指针指向的字符串中字符的个数,不管指针是数组的名字,还是个指针变量。

2、_strcpy和_strncpy函数

字符拷贝函数

函数定义:char*strcpy(char*dest,const char *src);

函数功能:拷贝src指向的字符串到dest指针指向的内存中,'\0'也会被拷贝;

返回值:目的内存的地址;

注意:使用此函数时,必须保证dest指向的内存足够大,否则会出现内存污染

例1:

#include<stdio.h>
#include<string.h>
int main()
{
    char str[100]="ababababababababababa";
    strcpy(str,"hello");
    printf("str=%s\n",str);

    return 0;
}

输出:str=hello

函数定义:char*strncpy(char*dest,const char *src,size_t n);

函数功能:将src指向的字符串前n个字节,拷贝到dest指向的内存中;

返回值:目的内存的首地址;

注意:strncpy不拷贝'\0';如果n大于src指向的字符串中的字符个数,则在dest后面填充n-strlen(src)个'\0';

例2:

#include<stdio.h>
#include<string.h>
int main()
{
    char buf[100]="abababababababa";
    strncpy(buf,"helloworld",5);
    printf("%s\n",buf);
    
    return 0;
}
输出:buf=helloabababababababa
3、_strcat和_strncat函数

字符追加函数

函数定义:char* strcat(char*dest,const char* src);

函数功能:strcat函数追加src字符串到dest指向的字符串的后面,追加在第一个\0的后面,\0前面的内容不懂

注意:保证dest指向的空间足够大

例:

#include<stdio.h>
#include<string.h>
int main()
{
    char str[20]="ab\0ababababababa";
    char *src="hello";
    strcat(str,src);
    printf("%s\n",str);
    return 0;
}
输出:str=abhello

函数定义:char* strncat(char*dest,const char* src,size_t n);

函数功能:追加src指向的字符串的前n个字符,追加到dest指向的字符的后面;

注意:如果n大于src字符串的字符个数,则只将src字符串追加到dest指向的字符串的后面,同样追加到第一个"\0"的后面。

例:

#include<stdio.h>
#include<string.h>
int main()
{
    char str[20]="ab\0ababababa";
    char *str="hello";
    strncpy(str,src,3);
    printf("%s\n",str);
    
    return 0;
}
输出:abhel
4、_strcmp和_strncmp函数

字符串比较函数

函数定义:int strcmp(const char*s1,const char*s2);

函数说明:比较字符串s1和s2的大小,逐个字符去比较ascII码,一旦比较出大小就返回;

返回值:

s1>s2,则返回1;s1<s2,则返回-1;s1=s2,则返回0;

函数定义:int strcmp(const char*s1,const char*s2,size_t n);

函数说明:比较s1和s2指向的字符串中的前n个字符;

5、_strchr和_strrchr函数

字符查找函数

函数定义:char* strchr(const char*s,int c);

函数说明:strchr 会从字符串 str 的起始位置开始,逐个字符检查,直到找到字符 c 或遇到字符串结束符 \0

返回值:返回找到字符的地址;未找到的话返回NULL;

函数定义:char* strrchr(const char*s,int c);

函数说明:strrchr 会从字符串 str 的起始位置开始,逐个字符检查,直到字符串结束符 \0,然后返回字符 c 最后一次出现的位置。

二者区别:

  • strchr 查找字符的第一次出现位置
  • strrchr 查找字符的最后一次出现位置
6、_strstr函数 

字符串匹配函数 

函数定义:char *strstr(const char *haystack, const char *needle);

参数说明:haystack:要搜索的主字符串(以 \0 结尾的字符数组);needle:要查找的子字符串(以 \0 结尾的字符数组)。

函数功能:strstr 会从主字符串 haystack 的起始位置开始,逐个字符检查,直到找到子字符串 needle 或遇到主字符串的结束符 \0

返回值:如果找到子字符串 needle,则返回指向主字符串 haystack 中子字符串第一次出现位置的指针;如果未找到子字符串 needle,则返回 NULL

 例:

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

int main() {
    const char *haystack = "Hello, World!";
    const char *needle = "World";

    // 查找子字符串 "World" 在主字符串中的位置
    char *result = strstr(haystack, needle);

    if (result != NULL) {
        printf("子字符串 '%s' 在主字符串中的位置是: %ld\n", needle, result - haystack);
        printf("从该子字符串开始的部分是: %s\n", result);
    } else {
        printf("子字符串 '%s' 未找到。\n", needle);
    }

    return 0;
}
输出:
子字符串 'World' 在主字符串中的位置是: 7
从该子字符串开始的部分是: World!
7、_memset函数

空间设定函数

函数定义:void*memset(void *ptr,int value,size_t num);

函数功能:memset函数是将ptr指向的内存空间的num个字节全部赋值为value参数;

ptr:只想任意类型的指针,即指向我们需要修改的内存;

value:给ptr指向的内存空间赋值;

num:确定将ptr所指向的内存中的num个字节全都用value代替

返回值:目的内存的首地址,即ptr的值;

例:

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

int main() {
    char str[50] = "Hello, World!";

    // 将 str 的前 5 个字节填充为 '*'
    memset(str, '*', 5);

    printf("填充后的字符串: %s\n", str);

    return 0;
}
输出:
填充后的字符串: *****, World!
8、atoi/atol/atof函数

字符串转换数值

 (1)_atol函数

函数原型:long atol(const char *str);

函数功能:将字符串转换为long型整数

返回值:返回转换后的long值,如果没法转换,返回0;

说明:

atol 的行为与 atoi 类似,但返回的是 long 类型。

适用于需要处理较大整数的场景。

例:

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

int main() {
    const char *str = "987654321";
    long num = atol(str);
    printf("转换后的长整数: %ld\n", num); // 输出: 987654321
    return 0;
}

(2)_atoi函数

函数原型:int atoi(const char *str);

函数功能:将字符串转换为int型整数;

返回值:返回转换后的int型,无法转换则返回0;

说明:

atoi 会忽略字符串开头的空白字符(如空格、制表符等)。

从第一个非空白字符开始解析,直到遇到非数字字符或字符串结束符 \0

如果字符串无法转换为有效的整数,则返回 0

例:

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

int main() {
    const char *str = "12345";
    int num = atoi(str);
    printf("转换后的整数: %d\n", num); // 输出: 12345
    return 0;
}

(3)_atof函数

函数原型:double atof(const char *str);

函数功能:将字符串转换为 double 类型的浮点数。

返回值:返回转换后的 double 值。如果字符串无法转换,则返回 0;

说明:

atof 会忽略字符串开头的空白字符;

从第一个非空白字符开始解析,直到遇到非浮点数字符或字符串结束符 \0;

支持科学计数法(如 "1.23e4");

 例:

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

int main() {
    const char *str = "3.14159";
    double num = atof(str);
    printf("转换后的浮点数: %f\n", num); // 输出: 3.141590
    return 0;
}
9、_strtok函数

字符串切割函数

函数原型:char *strtok(char *str, const char *delimiters);

函数功能:strtok 会从字符串 str 中查找分隔符,并将分隔符替换为 \0,从而将字符串分割成多个子字符串。每次调用 strtok 会返回一个子字符串的指针。

参数说明:

str

要分割的字符串(以 \0 结尾的字符数组)。如果是第一次调用 strtok,需要传入待分割的字符串。如果是后续调用,可以传入 NULL,表示继续分割同一个字符串。

delimiters

分隔符字符串(以 \0 结尾的字符数组)。函数会将 delimiters 中的每个字符都视为分隔符。

 例:

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

int main() {
    char str[] = "Hello,World!This is a test.";
    const char *delimiters = " ,!."; // 分隔符为空格、逗号、感叹号和句号

    // 第一次调用 strtok
    char *token = strtok(str, delimiters);
    while (token != NULL) {
        printf("子字符串: %s\n", token);
        // 后续调用 strtok
        token = strtok(NULL, delimiters);
    }

    return 0;
}
输出:
子字符串: Hello
子字符串: World
子字符串: This
子字符串: is
子字符串: a
子字符串: test

九、结构体

1、结构体类型定义

定义方法:

(1)先定义结构体类型,再去定义结构体变量

struct 结构体类型名{
    成员列表;
};


例:
struct stu{
    int num;
    char name[20];
    char sex;
}

在有了结构体类型后,就可以定结构体变量了:

struct stu jack,mike,lucy;
//定义了三个变量,分别为jack,mike,lucy
//其中,每个变量都包含三个成员,分别为num,name,sex

我们暂时认为结构体变量大小是所有成员变量大小之和;

(2)在定义结构体类型时顺便定义结构体变量

struct stu{
    int num;
    char name[20];
    char sex;
}jack,mike,lucy;

(3)在定义结构体类型时,没有结构体类型名,顺便定义结构体变量

因为没有类型名,以后不能再定义相关类型的数据了;

struct{
    int num;
    char name[20];
    char sex;
}jack,mike;
//此后不能再定义第三个变量了,除非回过头来加上去

(4)typedef替换结构体类型名简化代码

typedef struct stu{
    int num;
    char name[20];
    char sex;
}STU;

用STU替换结构体,STU就相当于整个结构体,以后定义结构体变量直接就是:

STU jack,mike,lucy;
2、结构体变量定义初始化及使用

(1)结构体变量的定义和初始化

struct stu{
    int num;
    char name[20];
    char sex;
};

int main()
{

    struct stu jack;
    struct stu mike={21,"mike","m"};
    
    return 0;
}

(2)结构体变量的使用

变量成员引用方法:

struct stu{
    int num;
    char name[20];
    char sex;
}jack,mike,lucy;

int main()
{
    jack.num=21;
    
    //这里如果报错用strcpy函数赋值
    jack.name="jack";
    jack.sex="m";
    //这里如果报错用strcpy函数赋值
    strcpy(jack.name,"jack");
    strcpy(jack.sex,"m");    
    
    
    return 0;
}

 这里直接给jack.name或者jack.sex赋值会报错,因为jack.name和jack.sex是个数组的名字,是常量,不能给常量赋值

(3)结构体成员嵌套使用

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


struct stu{
    int num;
    char name[20];
    char sex;
    struct birdate birthday; 
};


int main()
{
    struct stu jack={21,"jack","m"};
    jack.birthday.year=1830;
    jack.birthday.month=4;
    jack.birthday.day=9;

    return 0;
}
3、结构体变量相互赋值

只有相同类型的结构体变量可以相互赋值

#include<stdio.h>
struct sut{
    int num;
    char name[20];
    char sex;
};

int main()
{
    struct stu jack={21,"jack","m"};
    struct stu mike;
    
    //把jack的值赋给mike
    mike=jack;
    
    return 0;
}
4、结构体数组

结构体数组由若干个相同类型的结构体变量构成的集合

(1)结构体数组的定义方法

struct stu{
    int num;
    char name[20];
    char sex;
};

//定义了一个struct stu类的结构体数组peo
struct stu peo[3];
//包含三个元素peo[0]、peo[1]、peo[2];

 (2)结构体数组的引用方法

struct stu{
    int num;
    char name[20];
    char sex;
};

struct stu peo[3];

//直接引用
peo[0].num=21;
strcpy(peo[0].name,"jack");
5、结构体指针

结构体的地址,结构体变量存放内存中,也有起始地址,我们定义一个变量来存放这个首地址,那么这个变量就是结构体指针变量

(1)定义方法

struct stu{
    int num;
    char name[20];
    char sex;
};
//定义一个struct stu类型的指针变量p
struct stu* p;

//定义一个结构体变量jack
struct stu jack;

//访问方法
p=&jack;


(*p).num=21;
p->num=21;

通过指针引用结构体变量成员的前提:指针必须先指向一个结构体

(2)结构体指针应用场景

a.保存结构体变量的地址

typedef struct stu{
    int num;
    char name[20];
    char sex;
}STU;

int main()
{
    STU *p,jack;
    p=&jack;
    p->jack.num=21;
    strcpy(p->jack.name,"jack");

    return 0;
}

b.传结构体变量的地址

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

typedef struct stu{
    int num;
    char name[20];
    char sex;
}STU;

void function(STU* p)
{
    p->num=21;
    strcpy((*p).name,"jack");
    strcpy(p->sex,"m");
}

int main()
{
    STU jack;
    function(&jack);
    
    return 0;
}
 6、枚举

(1)枚举类型的定义方法

enum 枚举类型名{

        枚举值列表;

};

(2)枚举变量的定义方法

enum 枚举类型名 枚举变量名;

//定义枚举类型week
enum week
{
    mon,tue,wed,thu,fri,sat,sun
};

//定义了枚举变量workday和weekend;
enum workday,weekend;

//workday和weekend只能取mon,tue,...,sun;

注意:枚举值是常量,不能再赋值;

枚举变量是由系统定义的一个表示序号的数值,默认从0开始,如上方的从mon开始,mon为0,tue为1,......

我们可以改变枚举变量的默认值,如:

enum week
{
    mon=3,tue=2,wed=5,thu,fri,sat,sun
};

十、文件

1、文件指针

文件指针在程序中用来标识(代表)一个文件的,在打开文件的时候得到文件指针,文件指针就代表我们打开的文件;

我们对文件进行读、写、关闭等操作的时候,对文件指针进行操作即可,我们将文件指针,传给读、写、关闭等函数,函数对他们进行操作;

(1)定义文件指针的一般形式

FILE* 指针变量标识符;

(2)对文件操作的步骤

a.对文件进行读写之前要打开文件得到文件指针

b.可以通过文件指针对文件进行读写操作

c.读写等操作完毕后,要关闭文件,关闭文件后,就不能再通过此文件指针操作文件了

2、_fopen和_fclose函数

(1)_fopen函数 

函数声明:FILE*open(const char* path,const char* mode)

函数说明:打开一个已经存在的文件,并返回这个文件的文件指针。

函数参数:const char* path打开的文件的路径

const char* mode文件打开的方式(只读、只写、可读、可写等)

打开方式的几种形式:

r:以只读方式打开文件

文件不存在则返回NULL;

文件存在且成功打开,返回文件指针进行后续操作

FILE* fl;
fl=fopen("test.txt","r");

w:以只写方式打开文件

文件不存在,以指定文件名创建此文件,并且打开文件;

文件存在,清空文件内容,打开文件,然后进行写入操作;

文件打不开,比如只读,则返回NULL;

a:以追加方式打开文件

文件不存在,以指定文件名创建此文件;

文件存在,从文件的结尾处进行写入操作

+:同时以读写方式打开文件

(2)_fclose函数

函数声明:int fclose(FILE *fl);

函数说明:关闭fl指向的文件。

返回值:成功返回0,失败返回非0;

3、_fgetc和_fputc函数

一次读写一个字符 

(1)_fgetc

函数声明:int fgetc(FILE* stream);

函数说明:fgetc从stream所标识的文件中读取一个字节,将字节值返回

返回值:

以t的方式:读到文件结尾返回EOF;

以b的方式:读到文件结尾,使用feof(文件指针)判断结尾

(2)_fputc

函数声明:int fputc(int c,FILE* stream)

函数说明:fputc将c的值写到stream所代表的文件中;

返回值:

输入成功,返回输入的字节值;

输入失败,返回EOF;

4、_fgets和_fputs函数 

一次读写一个字符串 

 (1)_fgets

函数声明:char *fgets(char *str, int n, FILE *stream);

函数功能:

fgets 会从 stream 中读取最多 n-1 个字符,并在末尾自动添加 \0

如果在读取 n-1 个字符之前遇到换行符(\n),则停止读取,并将换行符包含在结果中。

如果到达文件末尾,则停止读取。

返回值:

成功时,返回 str 的指针。

失败或到达文件末尾时,返回 NULL

#include <stdio.h>

int main() {
    char buffer[100];
    printf("请输入一行文本: ");
    if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
        printf("你输入的内容是: %s", buffer);
    } else {
        printf("读取失败或到达文件末尾。\n");
    }
    return 0;
}

(2)_fputs

函数声明:int fputs(const char *str, FILE *stream);

函数功能:

fputs 会将字符串 str 写入到 stream 中,但不包括结尾的 \0

如果字符串中包含换行符(\n),则换行符也会被写入。

 返回值:

成功时,返回非负整数。

失败时,返回 EOF

#include <stdio.h>

int main() {
    const char *text = "Hello, World!\n";
    if (fputs(text, stdout) != EOF) {
        printf("字符串写入成功。\n");
    } else {
        printf("写入失败。\n");
    }
    return 0;
}
 5、_fread和_fwrite函数

fread 和 fwrite 是 C 标准库中用于二进制数据读写的函数,定义在头文件 <stdio.h> 中。它们分别用于从文件流中读取数据和向文件流中写入数据,适合处理二进制文件或大块数据。 

(1)_fread

函数原型:size_t fread(void *ptr, size_t size, size_t count, FILE *stream); 

 函数功能:

fread 会从 stream 中读取 size * count 字节的数据,并将其存储到 ptr 指向的内存块中。

适合读取二进制文件或大块数据。

返回值:

返回实际读取的数据项数量(可能小于 count)。

如果返回值小于 count,可能是到达文件末尾或发生错误。

#include <stdio.h>

int main() {
    FILE *file = fopen("data.bin", "rb");
    if (file == NULL) {
        perror("文件打开失败");
        return 1;
    }

    int buffer[10];
    size_t num_read = fread(buffer, sizeof(int), 10, file);
    printf("读取了 %zu 个整数。\n", num_read);

    fclose(file);
    return 0;
}

(2)_fwrite

函数原型:size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

函数功能:

fwrite 会将 ptr 指向的内存块中的 size * count 字节数据写入到 stream 中。

适合写入二进制文件或大块数据。

#include <stdio.h>

int main() {
    FILE *file = fopen("data.bin", "wb");
    if (file == NULL) {
        perror("文件打开失败");
        return 1;
    }

    int buffer[] = {1, 2, 3, 4, 5};
    size_t num_written = fwrite(buffer, sizeof(int), 5, file);
    printf("写入了 %zu 个整数。\n", num_written);

    fclose(file);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

天下第一银贼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值