C与指针.

目录

1_指针理解

1.1变量的值

1.2变量的地址

1.3指针

1.4取变量的地址

2_分析指针

2.1分析指针变量的要素

2.2根据需求定义指针变量

3_指针的使用

3.1指针对变量的读操作

3.2指针对变量的写操作

4_指针占用空间的大小与位移

4.1指针占用空间的大小

4.2指针的位移

5_指针用于传递参数

5.1值传递与地址传递

6_函数与指针

6.1函数指针

6.2指针函数

6.3区分

7_数组与指针

7.1数组的地址

7.2数组元素的指针使用

7.3一道小题目练习一下

7.4传入数组到子函数

7.5字符串与指针

7.6数组指针的使用

8_结构体与指针

8.1结构体指针

9_链表

9.1空间分配的方式

9.2空间动态分配管理函数

9.3链表理解

9.4创建链表

9.5表尾添加节点

9.6表头添加节点

9.7表中添加节点


1_指针理解

1.1变量的值

根据需要的数据类型定义变量,内存会给定义的变量分配空间,就可以这个空间写入值了。

int a = 5; //5就是变量的值

1.2变量的地址

定义变量时,内存会分配对应的空间,且该空间会有地址编号,变量的地址编号值为分配的空间的首字节地址编号值 。

1.3指针

指针是 一种数据类型,用指针类型定义的变量称为指针类型变量(或称指针变量、指针)

指针变量是用来存储变量地址编号值的。

1.4取变量的地址

在C中可用 '&'来取变量的地址,格式如下:

&变量名

int a = 5; //5就是变量的值

int *p = &a; //定义一个指针变量p来存储a的地址

2_分析指针

2.1分析指针变量的要素

指针变量本质是一个变量,只不过这种变量存储的内容是变量的地址编号值。

分析指针变量的三要素:

  1. 变量名
  2. 指针的类型
  3. 指向的对象类型

(这些例子简单一眼就能看出来,但后面的数组指针,函数指针,结构体指针就不一定了,不过方法都是一样的)

int *p

变量名 :p

指针类型 :int * (除了变量名以外的内容都是)

指向对象类型:int (除了 *变量名以外的内容就是)

float *q

变量名 :q

指针类型 :float *

指向对象类型:float

int (*p)[20]

变量名 :p

指针类型 :int(*) [20]

指向对象类型:int [20]

int **p

变量名 :p

指针类型 :int **

指向对象类型:int *

2.2根据需求定义指针变量

格式:
指向对象类型 *变量名

先确定指针指向的变量的类型,然后再定义。

int a; int *p; p = &a; //把a的地址存储到变量p中(指针变量p指向了变量a)

float b; float *p; p = &b; //把b的地址存储到变量p中(指针变量p指向了变量b)

char c; char *p; p = &c;

int *m; int **p; p = &m;

指针变量p 存储了变量a的地址;== 也可以说指针变量p指向了变量a;

3_指针的使用

3.1指针对变量的读操作

cs 复制代码
#include<stdio.h>

int main()
{
    int *p;                     //定义一个可以储存int类型的指针变量p
    int a = 10;                 //定义一个整型变量a,并给a负值10
    p = &a;                     //将a的地址储存到变量p中(指针变量p指向了变量a)
    printf("p  :%d\n",p);       //p储存了a的地址值
    printf("&a :%d\n",&a);      //可以看到直接打印出a的地址值与打印指针变量p的值是一样的
    printf("*p :%d\n",*p);      //打印出指针变量p指向的变量a的值
    printf("a  :%d\n",a);       //可以看到打印指针变量p指向的变量a的值与直接打印出a的值相同

    return 0;
}

注意:

int *p; 在定义语句中,* 可以理解为指针变量的标志

printf("*p :%d\n",*p); 中的* 是取内容符号

3.2指针对变量的写操作

cs 复制代码
#include<stdio.h>

int main()
{
    int *p;                         //定义一个可以储存int类型的指针变量p
    int a = 10;                     //定义一个整型变量a,并给a负值10
    p = &a;                         //将a的地址储存到变量p中(指针变量p指向了变量a)
    printf("赋值前\na: %d\n*p: %d\n",a,*p); //未赋值前a和*p都为10
    *p = 20;                        //将20赋值给*p.也就是将20赋值给a
    printf("赋值后\na: %d\n*p: %d\n",a,*p); //可以看到a和*p都变成了20

    return 0;
}

4_指针占用空间的大小与位移

4.1指针占用空间的大小

关键字:sizeof

功能:计算对应类型的变量占用空间的大小(字节)

格式:sizeof(变量类型或变量名)

指针变量占用空间的大小与指向对象类型没有关系

cs 复制代码
#include<stdio.h>

int main()
{
    printf("%d\n",sizeof(int*));
    printf("%d\n",sizeof(int*[20]));  //(指针数组)相当与20个int*占用的空间大小
    printf("%d\n",sizeof(int(*)[20]));//(数组指针)

    return 0;
}

指针变量占用的空间大小:

指针变量占用空间4byte(32位平台)

指针变量占用空间8byte(64位平台)

4.2指针的位移

指针位移就是指针变量增减

指针变量的位移与指向对象类型有关

!!!此段代码仅供举例,在实际操作中最好不要将不同类型的指针变量相互赋值!!!

!!!在这里因为指针变量的大小都一样,所以强制赋值没有问题!!!

cs 复制代码
#include<stdio.h>

int main()
{
    int a = 0x12345678;          // 定义一个整数变量 a,并初始化为 0x12345678
    int *p;              // 定义一个 int 类型的指针 p
    char *q;             // 定义一个 char 类型的指针 q
    p = &a;              // 将指针 p 指向变量 a 的地址
    q = p;               // 将指针 q 指向指针 p 所指向的地址(即 a 的地址)

    printf("位移前\n");
    printf("p: %d   q: %d\n", p, q); // 输出指针 p 和 q 的值
    printf("*p: 0x%x   *q: 0x%x\n", *p, *q);
    p += 1;              // 将指针 p 向后移动 1 个 int 类型的大小
    q += 1;              // 将指针 q 向后移动 1 个 char 类型的大小

    printf("位移后\n");
    printf("p: %d   q: %d\n", p, q); // 输出指针 p 和 q 的位移后的值
    printf("*p: 0x%x   *q: 0x%x\n", *p, *q);
    return 0;
}

通过结果可以看出

p的地址编号偏移了4,q的地址编号偏移了1。看图:

总结:指针的移位跟指向对象的数据类型有关

int *p

指针跳动一步,指针变量里存储的 地址编号就偏移4 地址编号+4

char *p

指针跳动一步,指针变量里存储的 地址编号就偏移1 地址编号+1

short *p

指针跳动一步,指针变量里存储的 地址编号就偏移2 地址编号+2

double *p

指针跳动一步,指针变量里存储的 地址编号就偏移8 地址编号+8


补充:(指针位移的数组应用的很广泛,因为数组元素的地址是连续的,在其他变量的用处实际不大)

p+1; //p储存的地址不变(p = p + 1;//这样p储存的地址才会改变)

p++;//p储存的地址改变


5_指针用于传递参数

5.1值传递与地址传递

值传递是将实参的值传递给函数的参数,在调用函数时,会对实参的值拷贝一份副本,程序只会在函数内部对形参进行操作,不会对原始变量(实参)进行修改。

cs 复制代码
#include<stdio.h>

// 声明函数 mm,接受两个参数:一个 int 类型和一个 char 类型
void mm(int a, char c);

int main(void)
{
    int x = 10; 
    char y = 'A'; 

    mm(x, y); // 调用 mm 函数,传递 x 和 y 的值(值传递)

    // 打印 x 的值,由于值传递,x 的值在 mm 函数中没有变化,仍然是 10
    printf("x:%d\n", x); 

    // 打印 y 的值,由于值传递,y 的值在 mm 函数中没有变化,仍然是 'A'
    printf("y:%c\n", y);

    return 0;  // 返回 0,表示程序正常结束
}

// 定义 mm 函数,接受两个参数:一个 int 类型的 a 和一个 char 类型的 c
void mm(int a, char c)
{
    // 在函数内部,a 被修改为原来 a 的值加 1,即 10 + 1 = 11
    a = a + 1;  
    
    // 在函数内部,c 被修改为原来 c 的值加 1,即 'A' 的 ASCII 值为 65,加 1 后变为 66,对应字符 'B'
    c = c + 1;

    // 打印修改后的 a 和 c 的值
    printf("a:%d\n", a);  // 输出 11,因为 a 被修改为 11
    printf("c:%c\n", c);  // 输出 'B',因为 c 被修改为 'B'
}

地址传递是将实参的地址(指针)传递给参数。在这种方式,函数的参数实际上指向了实参的地址,在调用函数时,会对原始变量(实参)进行操作。

cs 复制代码
#include<stdio.h>
void mm(int *a, char *c);

int main(void)
{
    int x = 10;
    char y = 'A';

    mm(&x, &y);  // 传递变量的地址

    printf("x: %d\n", x);  // 这里的 x 会被修改为 11
    printf("y: %c\n", y);  // 这里的 y 会被修改为 'B'

    return 0;
}

void mm(int *a, char *c)
{
    *a = *a + 1;  // 修改 a 指针指向的值
    *c = *c + 1;  // 修改 c 指针指向的值
    printf("a: %d\n", *a);  // 11
    printf("c: %c\n", *c);  // 'B'
}

地址传递使用场景:

  1. 想要在函数中改变实参的值。

  2. 想要获取子函数中的数据(特别是想要多个数据的时候)。ex: 子函数中寻找100~999中的所有水仙花数打印

    主函数要水仙花数的个数和总和

    cs 复制代码
    /*********************************************************************
    水仙花数(Narcissistic Number) 是指一个 n 位数,其每个数字的 n 次方和
    等于它本身。
    例如,三位数的水仙花数是指,某个三位数的每个数字的立方和等于这个数本身。
    **********************************************************************/
    #include<stdio.h>
    int sxh(int *s, int *c);
    
    int main(void)
    {
        int sum = 0,cont = 0;
        sxh(&sum,&cont);
    
        printf("水仙花数的和为:%d\n",sum);
        printf("水仙花数的个数:%d\n",cont);
    
        return 0;
    }
    
    int sxh(int *s, int *c)
    {
        int i,ge,shi,bai;
        printf("水仙花数有:\n");
        for(i = 100;i <= 999;i++)
        {
            ge  = (i / 1)   % 10;
            shi = (i / 10)  % 10;
            bai = (i / 100) % 10;
            if(ge*ge*ge + shi*shi*shi + bai*bai*bai == i)
            {
                printf("%d\n",i);
                *s += i;
                (*c)++;
            }
    
        }
        return 0;
    }
  3. 需要传递一个数组到子函数中。(看数组与指针部分)

6_函数与指针

6.1函数指针

指向对象类型是函数的指针叫函数指针,本质是指针。

作用:储存函数的地址变化值(函数的地址编号可以用函数名表示)

int (*f)(char a);

中间(*f)的括号必须加,不加就变成了指针函数,指针函数本质是函数。(后面会区分)

变量名 :f

指针类型 :int (*) (char a) (除了变量名以外的内容都是)

指向对象类型:int (char a) (除了 *变量名以外的内容就是)

作用: 存一个函数的地址,该函数的返回值为int类型且有一个char类型参数。

cs 复制代码
#include<stdio.h>

int mm(char a);
int main(void)
{
    printf("int(*)(char a)类型的指针占用空间的大小为%dbyte\n",sizeof(int(*)(char a)));
    int(*f)(char a);  //定义一个函数指针
    f = mm;           //将函数的地址赋值给指针变量f

    //通过函数名调用函数
    int a = mm('A');
    printf("'A'的ASCII值为:%d\n",a);

    //当通过函数指针调用
    int b = f('B');
    printf("'B'的ASCII值为:%d\n",b);

    //通过解引用函数指针调用
    int c = (*f)('C');
    printf("'C'的ASCII值为:%d\n",c);

    return 0;
}

int mm(char a)
{
    printf("进入int (char a)类型的函数\n");
    printf("函数的功能为打印字符%c并返回其ASCII值\n",a);
    return a;
}

多举2个例子:

int (*p)(void);

变量名 :p

指针类型 :int (*) (void) (除了变量名以外的内容都是)

指向对象类型:int (void) (除了 *变量名以外的内容就是)

作用: 存一个函数的地址,该函数的返回值为int类型且没有参数。

void (*q)(int a,char *b);

变量名 :q

指针类型 :void (*) (int a,char *b) (除了变量名以外的内容都是)

指向对象类型:void (int a,char *b) (除了 *变量名以外的内容就是)

作用: 存一个函数的地址,该函数无返回值为且有一个int类型和char *类型参数。

函数指针的使用场景:
将一个函数作为另一个函数的参数。

ex:通过接口函数启动功能函数。

cs 复制代码
#include<stdio.h>

void fun(void(*q)(void)); // 声明接口函数
void f1(void); // 声明功能函数1
void f2(void); // 声明功能函数2
void f3(void); // 声明功能函数3
void f4(void); // 声明功能函数4
void f5(void); // 声明功能函数5

int main(void)
{
    fun(f1); // 通过接口函数调用功能函数
    fun(f2);
    fun(f3);
    fun(f4);
    fun(f5);

    return 0;
}

// 接口函数:接收一个函数指针并调用对应的功能函数
void fun(void(*q)(void))
{
    (*q)(); // 调用传入的函数
}

// 功能函数1
void f1(void)
{
    printf("进入功能块1\n");
}

// 功能函数2
void f2(void)
{
    printf("进入功能块2\n");
}

// 功能函数3
void f3(void)
{
    printf("进入功能块3\n");
}

// 功能函数4
void f4(void)
{
    printf("进入功能块4\n");
}

// 功能函数5
void f5(void)
{
    printf("进入功能块5\n");
}

更多接口函数例子==》C语言_接口函数

6.2指针函数

指针函数本质是一个函数,一个可以返回地址编号的函数。

int *f(void);

函数名 : f

参数 :无

返回值 :int *

作用 : 该函数的返回值是地址编号,需要定义一个指针变量接收。

指针函数的使用场景:

用于动态分配。

int *malloc(int n);

具体看后面的链表章节

6.3区分

有括号的就是函数指针,没有括号的就是指针函数(类似数组指针和指针数组)。

分析

int (*mm)(void (*f)(float b), int a, char *m);

int *mm(void (*f)(float b), int a, char *m);

int *(*mm)(void (*f)(float b), int a, char *m);

int (*mm)(void (*f)(float b), int a, char *m);

函数指针:

变量名 :mm

指针类型 :int (*)(void(*f)(float b),int a,char *m)

指向对象类型:int (void(*f)(float b),int a,char *m)

这是一个指针变量,指针存储函数的地址,函数的要求如下:

返回值类型 : int

参数 : int a //传入一个整型值

char * m //传入一个字符型变量的地址

void(*f)(float b) //传入一个函数的地址,函数要求如下:

返回值: 无

参数 : float b //传入一个浮点值

cs 复制代码
#include <stdio.h>

// 定义一个简单的函数,接受一个 float 类型的参数
void example_function(float b) {
    printf("接收到的 float 值: %f\n", b);
}

// 定义一个函数,符合 mm 的签名,接收一个函数指针、一个整数和一个字符串
int my_function(void (*f)(float b), int a, char *m) {
    printf("整数: %d, 字符串: %s\n", a, m);
    f(3.14);  // 调用传入的函数 f
    return a * 2;
}

int main() {
    // 定义函数指针 mm,指向 my_function
    int (*mm)(void (*f)(float b), int a, char *m) = my_function;

    // 通过 mm 调用 my_function,并传入 example_function、整数 5 和字符串 "Hello, World!"
    int result = mm(example_function, 5, "Hello, World!");

    printf("结果: %d\n", result);
    return 0;
}

int *mm(void (*f)(float b), int a, char *m);

指针函数:

函数名 : mm

返回值 : int *

参数 : int a //传入一个整型值

char * m //传入一个字符型变量的地址

void(*f)(float b) //传入一个函数的地址,函数要求如下:

返回值: 无

参数 : float b //传入一个浮点值

cs 复制代码
#include <stdio.h>
#include <stdlib.h>

// 这是一个符合要求的函数,接收一个 float 类型的参数,并返回 void
void example_function(float b) {
    printf("函数 example_function 被调用,参数为: %f\n", b);
}

// mm 函数,返回一个 int* 指针
int* mm(void (*f)(float b), int a, char *m) {
    // 打印传入的整数和字符串
    printf("传入的整数 a: %d\n", a);
    printf("传入的字符串 m: %s\n", m);

    // 调用传入的函数 f,传入一个 float 参数
    f(3.14);

    // 使用 malloc 分配内存
    int *result = (int*)malloc(sizeof(int));
    if (result != NULL) {
        *result = a * 2;  // 计算并存储结果
    }
    return result;  // 返回指向结果的指针
}

int main() {
    // 定义一个函数指针 f,指向 example_function
    void (*f_ptr)(float) = example_function;

    // 调用 mm 函数,传入函数指针 f_ptr,整数 5 和字符串 "Hello"
    int *result = mm(f_ptr, 5, "Hello");

    // 打印 mm 函数返回的 int* 指针值和指针解引用后的值
    if (result != NULL) {
        printf("计算结果的指针地址: %p\n", (void*)result);
        printf("解引用后的结果: %d\n", *result);

        // 使用完 malloc 分配的内存后,记得释放它
        free(result);
    }

    return 0;
}

int *(*mm)(void (*f)(float b), int a, char *m);

函数指针:

变量名 :mm

指针类型 :int *(*)(void(*f)(float b),int a,char *m)

指向对象类型:int * (void(*f)(float b),int a,char *m)

这是一个指针变量,指针存储函数的地址,函数的要求如下:

返回值类型 : int *

参数 : int a //传入一个整型值

char * m //传入一个字符型变量的地址

void(*f)(float b) //传入一个函数的地址,函数要求如下:

返回值: 无

参数 : float b //传入一个浮点值

cs 复制代码
#include <stdio.h>
#include <stdlib.h>

// 这是一个符合要求的函数,接收一个 float 类型的参数,并返回 void
void example_function(float b) {
    printf("函数 example_function 被调用,参数为: %f\n", b);
}

// mm 函数的实现,符合声明
int *mm(void (*f)(float b), int a, char *m) {
    // 打印传入的整数和字符串
    printf("传入的整数 a: %d\n", a);
    printf("传入的字符串 m: %s\n", m);

    // 调用传入的函数 f,传入一个 float 参数
    f(3.14);

    // 计算 a * 2,并返回其地址
    int *result = (int *)malloc(sizeof(int));  // 动态分配内存
    if (result != NULL) {
        *result = a * 2;
    }
    return result;
}

int main() {
    // 定义一个函数指针 f,指向 example_function
    void (*f_ptr)(float) = example_function;

    // 定义一个函数指针 mm,指向 mm 函数
    int *(*mm_ptr)(void (*f)(float b), int a, char *m) = mm;

    // 调用 mm 函数,传入函数指针 f_ptr,整数 5 和字符串 "Hello"
    int *result = mm_ptr(f_ptr, 5, "Hello");

    // 打印 mm 函数返回的 int* 指针值和指针解引用后的值
    if (result != NULL) {
        printf("计算结果的指针地址: %p\n", (void*)result);
        printf("解引用后的结果: %d\n", *result);

        // 使用完 malloc 分配的内存后,记得释放它
        free(result);
    }

    return 0;
}

7_数组与指针

7.1数组的地址

!!!数组的首元素地址属性和数组的地址属性不一样!!!

数组的首元素地址:

0号元素的地址属性表示首个元素的地址

数组名代表数组首元素地址(或 &a[0])

偏移:a+1;偏移一个数组元素的长度地址

数组的地址:

数组的地址的属性表示整个数组的地址:&a

数组的首元素地址编号值和数组的地址编号值一样,但偏移量不一样

偏移:&a+1;偏移一个数组的长度地址,即 偏移量 == 元素个数 * 元素类型大小

7.2数组元素的指针使用

由于数组名可以代码数组首元素地址,所以通过:

(数组名+i)的形式表示数组的i号元素的地址

*(数组名+i)的形式来获取数组的i号元素的值

注意:

数组名只能代表数组首元素的地址,不能代表其他元素的地址

所以用数组名访问的时候,不能用 数组名++ 的形式

也可以通过指针变量来访问数组的元素:

通过指针操作数组中的元素,要先定义一个可以指向数组元素的指针,

然后,

通过p++的形式访问某个元素的地址 //指针变量p存储的地址是变化的

通过*p的形式访问某个元素的内容 //要注意指针某一时刻存了谁的地址

也可以

通过p+i的形式访问某个元素的地址 //指针变量p存的地址不变,一直是首元素地址

通过*(p+i)的形式访问某个元素的内容

7.3一道小题目练习一下

如果定义:

char a,b,c,d,e,f,x,y;

char niu[6];

niu[0] = 3;

niu[1] = 6;

niu[2] = 10;

niu[3] = 21;

niu[4] = 40;

niu[5] = 50;

char *sp = niu;

求:

a=*sp;

b=*sp+1;

c=*sp++;

d=*sp;

e=*(sp+1);

f=*sp;

x = sizeof(niu[6]);

y = sizeof(niu);

z = sizeof(char[6]);

k = sizeof(sp);

的结果(注意假设程序从上往下执行):

a= b= c= d=

e= f= x= y=

z= k=

cs 复制代码
#include<stdio.h>


int main(void)
{
    char a,b,c,d,e,f,x,y,z,k;
    char niu[6];
    niu[0] = 3;
    niu[1] = 6;
    niu[2] = 10;
    niu[3] = 21;
    niu[4] = 40;
    niu[5] = 50;
    char *sp = niu;

    a = *sp;            //sp指向niu[0]    a = niu[0] = 3
    b = *sp+1;          //sp指向niu[0]    b = niu[0]+1 = 4
    c = *sp++;          //sp指向niu[0]    c = niu[0] = 3  (*sp++ == *(sp++)    sp++运算符是先赋值后自增,所以本次赋值在自增前)
    d = *sp;            //sp指向niu[1]    d = niu[1] = 6  (上一行代码进行了自增)
    e = *(sp+1);        //sp指向niu[1],但(sp+1)的地址为niu[2]  e = niu[2] = 10
    f = *sp;            //sp指向niu[1]    f = niu[1] = 6
    x = sizeof(niu[6]);
    y = sizeof(niu);
    z = sizeof(char[6]);
    k = sizeof(sp);

    printf("a = %d\n",a);
    printf("b = %d\n",b);
    printf("c = %d\n",c);
    printf("d = %d\n",d);
    printf("e = %d\n",e);
    printf("f = %d\n",f);
    printf("x = %d\n",x);
    printf("y = %d\n",y);
    printf("z = %d\n",z);
    printf("k = %d\n",k);

    return 0;
}

思考:如果把代码中所以char改为int,指针的偏移有什么变换?(看4.2指针的位移

7.4传入数组到子函数

数组的空间特点:元素空间分配连续

基于数组的空间特点,我们可以吧数组的首元素地址传给子函数(子函数定义一个指针变量的形参来接收数组首元素地址),子函数就可以通过地址偏移的方式访问所以数组元素。

  1. 用户在主函数中定义一个数组,往数组中输入10个数据

写一个子函数,统计用户输入的数据非负数的个数打印

并且求非负数的和返回给主函数在主函数中打印。

cs 复制代码
#include <stdio.h>

int Sub(int *p, int n);

int main()
{
    int a[10];  // 存储用户输入的 10 个数
    int value;  // 存储非负数的和

    printf("请输入10个数:\n");
    // 输入 10 个整数
    for (int i = 0; i < 10; i++)
    {
        scanf("%d", &a[i]);
    }

    // 计算并返回非负数的和
    value = Sub(a, 10);

    // 输出非负数的和
    printf("非负数的和为:%d", value);

    return 0;
}

// 计算非负数的和
int Sub(int *p, int n)
{
    int sum = 0;  // 初始化和为 0

    // 输出非负数
    printf("非负数有:\n");
    // 遍历数组中的所有元素
    for (int i = 0; i < n; i++)  // 使用传入的数组大小 n
    {
        if (*(p + i) >= 0)  // 判断当前元素是否为非负数
        {
            printf("%d\n", *(p + i));  // 打印当前的非负数
            sum += *(p + i);  // 将非负数累加到 sum
        }
    }

    return sum;  // 返回非负数的和
}
  1. 用户在主函数中输入数组后

在子函数中去掉最大最小求平均值

返回平均值在主函数中打印

插个知识点:

冒泡排序

作用:将数组中的数据进行从大到小或者从小到大排序

原理:

int a[6] = { 68 , 100 , 90 , 34 , 200 , 60};

说明:轮数循环从1开始,每轮比较的次数 j == 数据个数 n - 轮数 i

每轮比较的次数从0开始,因为要用这个循环变量当数组的下标。

cs 复制代码
#include <stdio.h>


float Average(int *p, int n);

int main()
{
    int a[10];  
    float average;  
    printf("请输入10个数:\n");
    
    for (int i = 0; i < 10; i++)
    {
        scanf("%d", &a[i]);  /
    }

    average = Average(a, 10);
    printf("去掉最大最小后的平均值为%.2f\n", average);
    return 0;  
}

// 计算去掉最大最小数后的平均值的函数定义
float Average(int *p, int n)
{
    int temp, sum = 0;  // 临时变量 temp 用于交换,sum 初始化为 0,用来累加去掉最大最小数后的和
    float aver;  // 存储计算出的平均值
    
    // 冒泡排序,按升序排列数组
    for (int i = 1; i < n; i++)  // 外层循环:每次将最大的数移动到末尾
    {
        for (int j = 0; j < n - i; j++)  // 内层循环:比较相邻的两个数,较大的数交换到后面
        {
            if (p[j] > p[j + 1])  // 如果当前数比下一个数大,则交换
            {
                temp = p[j];  // 保存当前数
                p[j] = p[j + 1];  // 将下一个数赋值给当前数
                p[j + 1] = temp;  // 将保存的当前数赋值给下一个数
            }
        }
    }

    // 去掉最大值和最小值后,计算剩余部分的和
    for (int k = 1; k < n - 1; k++)  // 从第二个元素开始,到倒数第二个元素
    {
        sum += p[k];  // 累加每个元素到 sum
    }

    // 计算去掉最大最小数后的平均值
    aver = (float)sum / (n - 2);  // 计算平均值,确保进行浮点数运算

    return aver;  // 返回计算出的平均值
}

在上面2段代码中,关于在子函数调用数组我用了2中不同的形式:

用指针的形式:*(p+i)

用数组的形式:p[i]

虽然形式参数的类型是指针,但这两种方式是等价的,*(p + i) 等价于 p[i],

想想在用数组的时候是不是也用过指针的形式调用数组。


7.5字符串与指针

字符串其实是数组,用指针操作字符串其实就是用指针操作数组,在这不讲太多,可以看下这C_字符串其实就是字符数组

也可以看看下一节关于数组指针操作字符的二维数组的部分。

7.6数组指针的使用

区分:

数组指针: char (*p)[10]; 这是一个指针,可以存一个char [10]类型的数组的地址编号

指针数组: int *p[10]; 这是一个数组,可以存10个int * 类型的指针变量

有括号为指针,无括号为数组(跟函数指针和指针函数类似)

数组指针存了数组的地址编号,意味着整体操作数组,这多很多类型的数组没有操作价值,但可以操作字符数组,也就是操作字符串,一般用在字符的二维数组。

接下来讲讲数组指针操作字符的二维数组

①定义一个数组指针

char (*p)[10];

变量名 :p

指针类型 :char *[10]

指向对象类型:char [10]

②明确指向

假如有一个字符的二维数组:

char a[10][10];

p = a;

③使用

p++; //指针指向改变

p+1; //指针指向不变

练习:

主函数有一个指令包,指令包里有10个字符串指令,

用户再输入一个字符串指令,

写一个子函数,判断用户输入的字符串指令是否在指令包中,

如果在指令包中返回1,不在指令包中返回0。

分析:

10个指令存在一个二维数组中

主函数:

指令包二维数组

用户输入指令字符串

子函数:

参数:char (*p)[10], char *m

返回值: int

说明:

和二维数组中的每个字符串进行对比,对比成功返回1,失败返回0

cs 复制代码
#include <stdio.h>
#include <string.h>

int Judge(char (*p)[10],char *i);

int main()
{
    int judge;
    char package[10][10] = {"123456","abcd","98765",
                            "55555","4444","333","22",
                            "liao","jia","tong"};
    char ins[10];
    printf("请输入指令:");
    scanf("%s",ins);

    judge = Judge(package,ins);
    if(judge == 1)
    {
        printf("输入正确\n");
    }
    else if(judge == 0)
    {
        printf("输入错误\n");
    }

    return 0;
}



/*******************************************************
函数名     : Judge
函数功能   : 判断指令是否在指令包中
函数参数   : char (*p)[10], char *i
函数返回值 : int
函数描述   : 如果字符串i在字符串数组p中,则返回1,不在则返回0
*******************************************************/


int Judge(char (*p)[10], char *i)
{
    for (int j = 0; j < 10; j++) {
        if (strcmp(p[j], i) == 0) {  // 如果匹配
            return 1;  // 返回 1 表示输入正确
        }
    }
    return 0;  // 如果没有匹配,返回 0
}



//int Judge(char (*p)[10],char *i)
//{
//    int j = 0;
//    while(*(p+j) != NULL)
//    {
//        if(strcmp((char *)(p+j),i)) == 0)
//        {
//            return 1;
//        }
//        j++;
//    }
//    return 0;
//}

8_结构体与指针

关于结构体的基础知识在这里不讲,有需要可以看这==》C_结构体

8.1结构体指针

假如已经声明了一个结构体:

typedef struct book

{

char title[50]; // 书名

char author[50]; // 作者

char id[50]; // 书籍编号

float pop; // 热度

int stock; // 库存

float price; // 价格

} BOK; // 结构体类型的别名 BOK

定义一个结构体指针:

BOK *f;

变量名 :f

指针类型 :BOK *

指向对象类型:BOK

使用:

明确指向:

BOK bk1;

f = bk1;

格式:

这个符号: -> 是结构体指针特有的,是结构体元素到结构体具体成员的指向。

结构体指针变量名->成员变量名;

f->title f->price

cs 复制代码
#include <stdio.h>
#include <string.h>
typedef struct book
{
    char title[50];     // 书名
    char author[50];    // 作者
    char id[50];        // 书籍编号
    float pop;          // 热度
    int stock;          // 库存
    float price;        // 价格
} BOK;  // 结构体类型的别名 BOK

int main()
{
    BOK bk1 =
    {
        "C Programming",    // title
        "Dennis Ritchie",   // author
        "001",              // id
        4.5,                // pop
        10,                 // stock
        39.99               // price
    };
    
    BOK *f;
    f = &bk1;
    f -> price = 29.99;
    strcpy(f -> title , "C语言");
    
    printf("Book 1: %s by %s\n ID: %s\n Popularity: %.2f\n Stock: %d\n Price: %.2f\n",
           f->title, f->author, f->id, f->pop, f->stock, f->price);
    return 0;
}

9_链表

9.1空间分配的方式

自动分配:

系统根据用户定义的变量来分配空间,分配的位置为栈区。

访问可以通过变量访问,也可以通过地址访问。

动态分配:

用户通过动态分配函数人为申请空间,人为释放空间申请到的空间在堆区。

分配到的空间没有名字,只能通过地址访问。

9.2空间动态分配管理函数

malloc函数:

函数原型:

#include <stdlib.h> //头文件

void *malloc(unsigned int size); //函数

函数名 : malloc

函数参数 :unsigned int size

函数返回值 :void *

说明 :此函数有1个整型参数size,这个参数是用来请求分配的内存块大小的,

单位是字节(byte),此函数会返回一个地址编号,所在地址中存的数据类型 不确定。

功能 : 在堆区申请一块size字节的空间

会把申请到的空间的地址的首字节编号返回,如果分配失败则返回NULL

此空间没有类型(可以强转成任何地址类型)

注意 :申请空间是为了存数据

申请到的空间要类型转换

返回的是申请到的空间的首字节地址

cs 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 请求分配一个整数大小的内存
    int* ptr = (int*)malloc(sizeof(int));

    if (ptr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    // 使用分配的内存
    *ptr = 10;
    printf("存储的值是: %d\n", *ptr);

    // 释放分配的内存
    free(ptr);

    return 0;
}

free函数:

函数原型:

#include <stdlib.h> //头文件

void free(void *prt); //函数

函数名 :free

函数参数 :void *prt

函数返回值 :无

功能 : 释放指针指向的堆区空间的内容

权限自由

注意 :释放的是空间里的内容,不是空间

调用后,*prt的值发生了变化,因为这块空间里的内容已经释放掉了,但prt还是

指向这块空间的首地址,不太好,所以最好:

prt = NULL; //避免"野指针"

cs 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 动态分配内存
    int* ptr = (int*)malloc(sizeof(int));

    if (ptr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    // 使用分配的内存
    *ptr = 42;
    printf("存储的值是: %d\n", *ptr);

    // 释放分配的内存
    free(ptr);
    
    // 注意:释放后,不要再使用该指针
    ptr = NULL;  // 设为NULL,避免悬空指针问题

    return 0;
}

calloc函数:

#include <stdlib.h> //头文件

void *calloc(unsigned int num,unsigned int size); //函数

函数名 : calloc

函数参数 :unsigned int num,unsigned int size

函数返回值 :void *

说明 :此函数有2个整型参数 num和size,num是要申请的内存块数,size是用来请求分 配的每块内存块大小的,单位是字节(byte)。

功能 : 在堆区申请num块size字节的空间

会把申请到的空间的地址的首字节编号返回,如果分配失败则返回NULL

此空间没有类型(可以强转成任何地址类型)

cs 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    // 分配并初始化 10 个整数大小的内存块
    int* ptr = (int*)calloc(10, sizeof(int));

    if (ptr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    // 打印每个元素的值(应为 0,因为 calloc 初始化了内存)
    for (int i = 0; i < 10; i++) {
        printf("ptr[%d] = %d\n", i, ptr[i]);
    }

    // 释放分配的内存
    free(ptr);

    return 0;
}

callocmalloc的区别主要是以下两点:

1.calloc是申请num块字节数为size的内存空间。

malloc是申请1块字节数为size的内存空间

calloc(6,4); 等价于malloc(24); //在堆区申请24字节的空间

2.malloc不会初始化申请到的空间,而calloc会初始化申请到的空间。

9.3链表理解

数组的特点:空间连续,只要知道首元素地址,就可以访问每个元素

缺点:需要提前分配固定大小的空间,一旦分配大小就不能改变

空间分配小了不够用

空间分配大了浪费

而链表可以克服数组的缺点:

数组的特点:由一个个节点组成,可以通过第一个节点就能访问所以节点(节点存储的是 数据信息)

每个节点是动态分配的,动态分配的空间不连续

将每个节点连接起来就是链表

优点:节点空间可以随时申请,弥补了数组的固定空间造成浪费的缺点

思考:

节点空间是不连续的,如何将节点连接起来?

每个节点包含两部分:数据部分指针部分。指针部分存储的是下一个节点的位置(即内存地址),因此它将节点在内存中连接起来,形成一个链式结构。

节点既要存地址,还要存变量,所以每个节点相当于与一个结构体变量,只不过没有名字。

每个指针也都是结构体指针。

  • 节点1 ,它的指针指向节点2
  • 节点2 ,它的指针指向节点3
  • 节点3 ,它的指针指向NULL(表示链表的结束)。

如何访问每个节点的成员信息?

首先定义一个结构体指针变量 p p = 表头的地址

p -> 数据成员 //访问成员信息

p = p -> 下一个节点的地址 // 此时p 指向了下一个节点的地址

p -> 数据成员 //访问成员信息

p = p -> 下一个节点的地址 // 此时p 指向了下一个节点的地址

.

.

.

.

先理解,具体操作看下文。

9.4创建链表

创建链表的过程:

申请表头 申请节点 挂载节点

(申请一个节点就挂载一个节点)

挂载:挂载在表尾,挂载在表头,挂载在链表中间

创建表头:

表头是一个节点

节点是结构体类型

结构体类型有数据成员和下一个节点的地址成员

每个节点应该用动态分配函数分配

分配完要对数据成员赋值

写一个创建表头函数:

分析:

动态分配空间

//成员信息赋值

返回表头地址

函数名: creat_list_head

参数 : 无

返回值: 结构体指针类型(先声明一个结构体类型)

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct book
{
    char title[50];     // 书名
    char author[50];    // 作者
    char id[50];        // 书籍编号
    float pop;          // 热度
    int stock;          // 库存
    float price;        // 价格

    struct book *next;  //用来存储下一个节点
} BOK;  // 结构体类型的别名 BOK


BOK *creat_list_head(void);

int main()
{
    BOK *head;
    head = creat_list_head();

    return 0;
}


/*******************************************************
函数名     : creat_list_head
函数功能   : 创建链表表头
函数参数   : 无
函数返回值 : BOK *
函数描述   : 返回表头地址
*******************************************************/

BOK *creat_list_head(void)
{
    BOK *h;
    //动态分配空间
    h = (BOK*)malloc(sizeof(BOK));
    h -> next = NULL;
    //成员信息赋值
    //表头(头节点)通常是用来表示链表的开始,而不存储实际的数据。

    printf("表头创建成功\n");

    //返回表头地址
    return h;
}

9.5表尾添加节点

写一个挂载在表尾的函数:

遍历每个节点直到表尾节点(表尾节点的next成员一定为NULL)

动态分配节点空间

给节点数据成员赋值

将新的节点的地址给原表尾的next成员

函数名: add_end

参数 : BOK *h -- 链表头节点的地址

返回值: 无

写一个打印链表信息的函数:

跳过表头节点,开始遍历实际的书籍节点

遍历链表,输出每本书的详细信息

移动到下一个节点

函数名: printf_list

参数 : BOK *h -- 链表头节点的地址

返回值: 无

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义书籍信息结构体
typedef struct book
{
    char title[50];     // 书名
    char author[50];    // 作者
    char id[50];        // 书籍编号
    float pop;          // 热度
    int stock;          // 库存
    float price;        // 价格

    struct book *next;  // 指向下一个书籍节点
} BOK;  // 结构体类型的别名 BOK


// 函数声明
BOK *creat_list_head(void);
void add_end(BOK *h);
void printf_list(BOK *h);

int main()
{
    BOK *head;
    head = creat_list_head();  // 创建表头
    add_end(head);             // 添加书籍到链表尾部
    printf_list(head);         // 打印链表中所有书籍信息

    return 0;
}

/*******************************************************
函数名     : creat_list_head
函数功能   : 创建链表头节点
函数参数   : 无
函数返回值 : BOK *
函数描述   : 创建一个链表头节点,返回头节点地址
*******************************************************/
BOK *creat_list_head(void)
{
    BOK *h;
    // 动态分配内存为链表头节点
    h = (BOK*)malloc(sizeof(BOK));
    h->next = NULL;  // 初始化链表头节点的next指针为NULL

    printf("表头创建成功\n");

    // 返回链表头节点的地址
    return h;
}

/*******************************************************
函数名     : add_end
函数功能   : 在链表尾部添加书籍信息
函数参数   : BOK *h -- 链表头节点的地址
函数返回值 : 无
函数描述   : 传入链表头节点,通过遍历链表找到尾部,然后添加新的书籍信息
*******************************************************/
void add_end(BOK *h)
{
    BOK *new;

    // 遍历链表找到尾节点
    while (h->next != NULL)
    {
        h = h->next;  // 移动到下一个节点
    }

    // 循环结束后,h指向的是链表的尾节点

    printf("请输入书的信息:\n");

    while(1)
    {
        // 为新书籍分配内存
        new = (BOK*)malloc(sizeof(BOK));
        new->next = NULL;  // 新节点的next指针初始化为NULL

        // 输入书籍信息
        printf("书名: ");
        scanf("%s", new->title);  // 输入书名

        printf("作者: ");
        scanf("%s", new->author); // 输入作者

        printf("id: ");
        scanf("%s", new->id);     // 输入书籍编号

        printf("热度: ");
        scanf("%f", &new->pop);   // 输入书籍热度

        printf("库存: ");
        scanf("%d", &new->stock); // 输入库存数量

        printf("价格: ");
        scanf("%f", &new->price); // 输入书籍价格

        // 将新书籍节点添加到链表尾部
        h->next = new;

        h = new; // 更新h为新节点,h指向链表的尾部

        // 询问是否继续添加书籍
        printf("继续申请按【y】\n退出按其它任意键\n");

        // 判断用户是否继续输入
        if (getch() != 'y')
        {
            break;  // 输入不是'y',则退出循环
        }
    }
}

/*******************************************************
函数名     : printf_list
函数功能   : 打印链表中所有书籍的信息
函数参数   : BOK *h -- 链表头节点的地址
函数返回值 : 无
函数描述   : 通过链表头节点,遍历链表并输出每本书的详细信息
*******************************************************/
void printf_list(BOK *h)
{
    h = h->next;  // 跳过表头节点,开始遍历实际的书籍节点

    // 遍历链表,输出每本书的详细信息
    while (h != NULL)
    {
        // 打印书籍信息
        printf("书名:%s\n作者:%s\n书籍编号:%s\n热度:%.1f\n库存:%d\n价格:%.2f\n\n",
               h->title, h->author, h->id, h->pop, h->stock, h->price);

        h = h->next;  // 移动到下一个节点
    }
}

9.6表头添加节点

写一个在表头添加节点函数:

动态分配一个空间给新表头

将旧表头的地址挂载在新表头的next下

给新表头节点数据成员赋值

更新新的表头地址

函数名: add_head

参数 : BOK *h -- 旧链表头节点的地址

返回值: BOK * -- 新链表头节点的地址

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义书籍信息结构体
typedef struct book
{
    char title[50];     // 书名
    char author[50];    // 作者
    char id[50];        // 书籍编号
    float pop;          // 热度
    int stock;          // 库存
    float price;        // 价格

    struct book *next;  // 指向下一个书籍节点
} BOK;  // 结构体类型的别名 BOK


// 函数声明
BOK *creat_list_head(void);
void add_end(BOK *h);
BOK *add_head(BOK *h);
void printf_list(BOK *h);


int main()
{
    BOK *head;
    head = creat_list_head();  // 创建表头
//    add_end(head);             // 添加书籍到链表尾部
    head = add_head(head);            // 添加书籍到链表表头
    printf_list(head);         // 打印链表中所有书籍信息

    return 0;
}

/*******************************************************
函数名     : creat_list_head
函数功能   : 创建链表头节点
函数参数   : 无
函数返回值 : BOK *
函数描述   : 创建一个链表头节点,并添加第一本书籍信息到表头,
             返回头节点地址
*******************************************************/
BOK *creat_list_head(void)
{
    BOK *h;
    // 动态分配内存为链表头节点
    h = (BOK*)malloc(sizeof(BOK));
    h->next = NULL;  // 初始化链表头节点的next指针为NULL

    printf("表头创建成功\n");

    printf("\n请输入书的信息:\n");
    // 输入书籍信息
    printf("书名: ");
    scanf("%s", h->title);  // 输入书名

    printf("作者: ");
    scanf("%s", h->author); // 输入作者

    printf("id: ");
    scanf("%s", h->id);     // 输入书籍编号

    printf("热度: ");
    scanf("%f", &h->pop);   // 输入书籍热度

    printf("库存: ");
    scanf("%d", &h->stock); // 输入库存数量

    printf("价格: ");
    scanf("%f", &h->price); // 输入书籍价格

    // 返回链表头节点的地址
    return h;
}

/*******************************************************
函数名     : add_end
函数功能   : 在链表尾部添加书籍信息
函数参数   : BOK *h -- 链表头节点的地址
函数返回值 : 无
函数描述   : 传入链表头节点,通过遍历链表找到尾部,然后添加新的书籍信息
*******************************************************/
void add_end(BOK *h)
{
    BOK *new;

    // 遍历链表找到尾节点
    while (h->next != NULL)
    {
        h = h->next;  // 移动到下一个节点
    }

    // 循环结束后,h指向的是链表的尾节点


    while(1)
    {
        printf("\n请输入书的信息:\n");
        // 为新书籍分配内存
        new = (BOK*)malloc(sizeof(BOK));
        new->next = NULL;  // 新节点的next指针初始化为NULL

        // 输入书籍信息
        printf("书名: ");
        scanf("%s", new->title);  // 输入书名

        printf("作者: ");
        scanf("%s", new->author); // 输入作者

        printf("id: ");
        scanf("%s", new->id);     // 输入书籍编号

        printf("热度: ");
        scanf("%f", &new->pop);   // 输入书籍热度

        printf("库存: ");
        scanf("%d", &new->stock); // 输入库存数量

        printf("价格: ");
        scanf("%f", &new->price); // 输入书籍价格

        // 将新书籍节点添加到链表尾部
        h->next = new;

        h = new; // 更新h为新节点,h指向链表的尾部

        // 询问是否继续添加书籍
        printf("继续申请按【y】\n退出按其它任意键\n");

        // 判断用户是否继续输入
        if (getch() != 'y')
        {
            break;  // 输入不是'y',则退出循环
        }
    }
}
/*******************************************************
函数名     : add_head
函数功能   : 在链表表头添加书籍信息
函数参数   : BOK *h -- 旧链表头节点的地址
函数返回值 : BOK *  -- 新链表头节点的地址
函数描述   : 传入旧链表头节点,在旧表头前添加一个新表头,
             返回新表头的地址
*******************************************************/

BOK *add_head(BOK *h)
{

    while(1)
    {
        BOK *new;
        //动态分配一个空间给新表头
        new = (BOK*)malloc(sizeof(BOK));

        //将旧表头的地址挂载在新表头的next下
        new->next = h;
        //给新表头节点数据成员赋值
        printf("\n请输入书的信息:\n");
         // 输入书籍信息
        printf("书名: ");
        scanf("%s", new->title);  // 输入书名

        printf("作者: ");
        scanf("%s", new->author); // 输入作者

        printf("id: ");
        scanf("%s", new->id);     // 输入书籍编号

        printf("热度: ");
        scanf("%f", &new->pop);   // 输入书籍热度

        printf("库存: ");
        scanf("%d", &new->stock); // 输入库存数量

        printf("价格: ");
        scanf("%f", &new->price); // 输入书籍价格

        //更新新的表头地址
        h = new;

        // 询问是否继续添加书籍
        printf("继续申请按【y】\n退出按其它任意键\n");

        // 判断用户是否继续输入
        if (getch() != 'y')
        {
            break;  // 输入不是'y',则退出循环
        }
    }
    return h;
}

/*******************************************************
函数名     : printf_list
函数功能   : 打印链表中所有书籍的信息
函数参数   : BOK *h -- 链表头节点的地址
函数返回值 : 无
函数描述   : 通过链表头节点,遍历链表并输出每本书的详细信息
*******************************************************/
void printf_list(BOK *h)
{
//    h = h->next;  // 跳过表头节点,开始遍历实际的书籍节点

    // 遍历链表,输出每本书的详细信息
    while (h != NULL)
    {
        // 打印书籍信息
        printf("\n书名:%s\n作者:%s\n书籍编号:%s\n热度:%.1f\n库存:%d\n价格:%.2f\n\n",
               h->title, h->author, h->id, h->pop, h->stock, h->price);

        h = h->next;  // 移动到下一个节点
    }
}

9.7表中添加节点

写一个挂载在表尾的函数:

创建新的节点

对新的节点进行赋值

根据某种条件遍历链表找到符合条件的节点位置

将新的节点挂载在符合条件的节点后面

(如找不到符合条件的位置旧挂载在表尾)

函数名: add_list

参数 : BOK *h -- 链表头节点的地址

返回值: 无

cs 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 定义书籍信息结构体
typedef struct book
{
    char title[50];     // 书名
    char author[50];    // 作者
    char id[50];        // 书籍编号
    float pop;          // 热度
    int stock;          // 库存
    float price;        // 价格

    struct book *next;  // 指向下一个书籍节点
} BOK;  // 结构体类型的别名 BOK


// 函数声明
BOK *creat_list_head(void);
void add_end(BOK *h);
BOK *add_head(BOK *h);
void printf_list(BOK *h);


int main()
{
    BOK *head;
    head = creat_list_head();  // 创建表头
//    add_end(head);             // 添加书籍到链表尾部
    head = add_head(head);       // 添加书籍到链表表头
    printf_list(head);         // 打印链表中所有书籍信息

    return 0;
}

/*******************************************************
函数名     : creat_list_head
函数功能   : 创建链表头节点
函数参数   : 无
函数返回值 : BOK *
函数描述   : 创建一个链表头节点,并添加第一本书籍信息到表头,
             返回头节点地址
*******************************************************/
BOK *creat_list_head(void)
{
    BOK *h;
    // 动态分配内存为链表头节点
    h = (BOK*)malloc(sizeof(BOK));
    h->next = NULL;  // 初始化链表头节点的next指针为NULL

    printf("表头创建成功\n");

    printf("\n请输入书的信息:\n");
    // 输入书籍信息
    printf("书名: ");
    scanf("%s", h->title);  // 输入书名

    printf("作者: ");
    scanf("%s", h->author); // 输入作者

    printf("id: ");
    scanf("%s", h->id);     // 输入书籍编号

    printf("热度: ");
    scanf("%f", &h->pop);   // 输入书籍热度

    printf("库存: ");
    scanf("%d", &h->stock); // 输入库存数量

    printf("价格: ");
    scanf("%f", &h->price); // 输入书籍价格

    // 返回链表头节点的地址
    return h;
}

/*******************************************************
函数名     : add_end
函数功能   : 在链表尾部添加书籍信息
函数参数   : BOK *h -- 链表头节点的地址
函数返回值 : 无
函数描述   : 传入链表头节点,通过遍历链表找到尾部,然后添加新的书籍信息
*******************************************************/
void add_end(BOK *h)
{
    BOK *new;

    // 遍历链表找到尾节点
    while (h->next != NULL)
    {
        h = h->next;  // 移动到下一个节点
    }

    // 循环结束后,h指向的是链表的尾节点


    while(1)
    {
        printf("\n请输入书的信息:\n");
        // 为新书籍分配内存
        new = (BOK*)malloc(sizeof(BOK));
        new->next = NULL;  // 新节点的next指针初始化为NULL

        // 输入书籍信息
        printf("书名: ");
        scanf("%s", new->title);  // 输入书名

        printf("作者: ");
        scanf("%s", new->author); // 输入作者

        printf("id: ");
        scanf("%s", new->id);     // 输入书籍编号

        printf("热度: ");
        scanf("%f", &new->pop);   // 输入书籍热度

        printf("库存: ");
        scanf("%d", &new->stock); // 输入库存数量

        printf("价格: ");
        scanf("%f", &new->price); // 输入书籍价格

        // 将新书籍节点添加到链表尾部
        h->next = new;

        h = new; // 更新h为新节点,h指向链表的尾部

        // 询问是否继续添加书籍
        printf("继续申请按【y】\n退出按其它任意键\n");

        // 判断用户是否继续输入
        if (getch() != 'y')
        {
            break;  // 输入不是'y',则退出循环
        }
    }
}
/*******************************************************
函数名     : add_head
函数功能   : 在链表表头添加书籍信息
函数参数   : BOK *h -- 旧链表头节点的地址
函数返回值 : BOK *  -- 新链表头节点的地址
函数描述   : 传入旧链表头节点,在旧表头前添加一个新表头,
             返回新表头的地址
*******************************************************/

BOK *add_head(BOK *h)
{
    BOK *new;
    while(1)
    {

        //动态分配一个空间给新表头
        new = (BOK*)malloc(sizeof(BOK));

        //将旧表头的地址挂载在新表头的next下
        new->next = h;
        //给新表头节点数据成员赋值
        printf("\n请输入书的信息:\n");
         // 输入书籍信息
        printf("书名: ");
        scanf("%s", new->title);  // 输入书名

        printf("作者: ");
        scanf("%s", new->author); // 输入作者

        printf("id: ");
        scanf("%s", new->id);     // 输入书籍编号

        printf("热度: ");
        scanf("%f", &new->pop);   // 输入书籍热度

        printf("库存: ");
        scanf("%d", &new->stock); // 输入库存数量

        printf("价格: ");
        scanf("%f", &new->price); // 输入书籍价格

        //更新新的表头地址
        h = new;

        // 询问是否继续添加书籍
        printf("继续申请按【y】\n退出按其它任意键\n");

        // 判断用户是否继续输入
        if (getch() != 'y')
        {
            break;  // 输入不是'y',则退出循环
        }
    }
    return h;
}

/*******************************************************
函数名     : add_list
函数功能   : 在链表中间插入节点
函数参数   : BOK *h -- 链表头节点的地址
函数返回值 : 无
函数描述   : 
*******************************************************/

void add_list(BOK *h)
{
    BOK *new;
    while(1)
    {
        //创建新的节点
        new = (BOK*)malloc(sizeof(BOK));

        //对新的节点进行赋值 
        printf("\n请输入书的信息:\n");
         // 输入书籍信息
        printf("书名: ");
        scanf("%s", new->title);  // 输入书名

        printf("作者: ");
        scanf("%s", new->author); // 输入作者

        printf("id: ");
        scanf("%s", new->id);     // 输入书籍编号

        printf("热度: ");
        scanf("%f", &new->pop);   // 输入书籍热度

        printf("库存: ");
        scanf("%d", &new->stock); // 输入库存数量

        printf("价格: ");
        scanf("%f", &new->price); // 输入书籍价格
        //条件为书籍热度相同
        while(h->next != NULL)  //(如找不到符合条件的位置旧挂载在表尾)
        {
            if(new->pop == h->pop)
            {
                break;
            }
            h = h->next;
        }
        //将新的节点挂载在符合条件的节点后面
        new->next = h->next;
        h ->next = new;
        // 判断用户是否继续输入
        if (getch() != 'y')
        {
            break;  // 输入不是'y',则退出循环
        }
    }

}
/*******************************************************
函数名     : printf_list
函数功能   : 打印链表中所有书籍的信息
函数参数   : BOK *h -- 链表头节点的地址
函数返回值 : 无
函数描述   : 通过链表头节点,遍历链表并输出每本书的详细信息
*******************************************************/
void printf_list(BOK *h)
{
//    h = h->next;  // 跳过表头节点,开始遍历实际的书籍节点

    // 遍历链表,输出每本书的详细信息
    while (h != NULL)
    {
        // 打印书籍信息
        printf("\n书名:%s\n作者:%s\n书籍编号:%s\n热度:%.1f\n库存:%d\n价格:%.2f\n\n",
               h->title, h->author, h->id, h->pop, h->stock, h->price);

        h = h->next;  // 移动到下一个节点
    }
}
相关推荐
朱小勇本勇12 分钟前
Qt同步读取串口
开发语言·qt
木子欢儿44 分钟前
Windows Server 2019 配置PHP环境(图文教程)
开发语言·windows·adb·php
小小小栋1 小时前
VSCode配置C语言开发环境
c语言·ide·vscode·msys2
明月看潮生1 小时前
青少年编程与数学 02-004 Go语言Web编程 03课题、项目调试
开发语言·青少年编程·编程与数学·goweb
别发呆了吧1 小时前
封装上传组件的详细步骤
开发语言·前端·javascript
_小许_1 小时前
Go语言后台实现选中式导出excel文件
开发语言·golang·excel
见未见过的风景1 小时前
启动报错java.lang.NoClassDefFoundError: ch/qos/logback/core/status/WarnStatus
java·开发语言·logback
silver6872 小时前
使用C语言连接MySQL
c语言
抓哇FullStack-Junior2 小时前
设计模式——建造者模式
java·开发语言·设计模式·建造者模式
前端郭德纲2 小时前
React 19有哪些新特性?
开发语言·javascript·react.js