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指针对变量的读操作

#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指针对变量的写操作

#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(变量类型或变量名)

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

#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指针的位移

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

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

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

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

#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值传递与地址传递

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

#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'
}

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

#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中的所有水仙花数打印

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

    /*********************************************************************
    水仙花数(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类型参数。

#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:通过接口函数启动功能函数。

#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       //传入一个浮点值

              

#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       //传入一个浮点值

#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       //传入一个浮点值

#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=

#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个数据

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

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

#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;  // 返回非负数的和
}

 2.      用户在主函数中输入数组后

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

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

插个知识点:

冒泡排序

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

原理:

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

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

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

#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

#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

#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

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

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

                             申请到的空间要类型转换

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

#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;    //避免”野指针“

                              

#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

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

#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链表理解

还没写b( ̄▽ ̄)d 

9.4创建链表

9.5表尾添加节点

9.6表头添加节点

9.7表中添加节点

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/927027.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法与数据结构(1)

一&#xff1a;数据结构概论 数据结构分为初阶数据结构&#xff08;主要由C语言实现&#xff09;和高阶数据结构&#xff08;由C实现&#xff09; 初阶数据结构当中&#xff0c;我们会学到顺序表、链表、栈和队列、二叉树、常见排序算法等内容。 高阶数据结构当中&#xff0…

Oracle12.2 RAC集群管理修改IP地址(DNS解析)

Oracle12.2 RAC集群管理之修改IP地址 该章节实验是基于此章节基础上操作&#xff1a; Oracle LinuxR7安装Oracle 12.2 RAC集群实施&#xff08;DNS解析&#xff09;-CSDN博客 环境 改前IP&#xff1a; 172.30.21.101 hefei1 hefei1.hefeidb.com 172.30.21.102 hefei2 …

【阅读笔记】Android广播的处理流程

关于Android的解析&#xff0c;有很多优质内容&#xff0c;看了后记录一下阅读笔记&#xff0c;也是一种有意义的事情&#xff0c; 今天就看看“那个写代码的”这位大佬关于广播的梳理&#xff0c; https://blog.csdn.net/a572423926/category_11509429.html https://blog.c…

基于rpcapd与wireshark的远程实时抓包的方法

基于rpcapd与wireshark的远程实时抓包的方法 服务端安装wireshark侧设置 嵌入式设备或服务器上没有图形界面&#xff0c;通常使用tcpdump抓包保存为pcap文件后&#xff0c;导出到本地使用wireshark打开分析&#xff0c;rpcapd可与wireshark配合提供一种远程实时抓包的方案&…

SpringBoot3如何基于ServletRequestHJandledEvent检测接口响应时间以及对应的参数

在 Spring Boot 3 中&#xff0c;可以通过实现 ServletRequestHandledEvent 事件来监测接口的响应时间以及相关的参数。ServletRequestHandledEvent 是 Spring 的应用事件之一&#xff0c;它在请求处理完成时发布&#xff0c;包含有关请求的信息。 以下是一个步骤指南&#xff…

MyBatis快速入门(中)

MyBatis快速入门&#xff08;中&#xff09; 四、全局配置文件configuration标签中子标签顺序1、子标签顺序2、子标签说明3、<properties> 标签和 <environments> 标签详述4、配置文件代码示例 五、MyBatis 基础功能之 resultMap1、建表语句2、解决表中字段名和类中…

【热门主题】000069 服务器虚拟化:开启高效资源管理新时代

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【热…

day22:lamp项目部署

一&#xff0c;lamp概述 lamp概述 LAMP 是一组开源软件的缩写&#xff0c;用于搭建动态网站或Web应用程序的基础环境。LAMP 代表了四个主要的组成部分&#xff1a; Linux&#xff1a;操作系统&#xff0c;LAMP 环境的基础。通常使用的是 Linux 发行版&#xff0c;如 CentOS、…

通俗易懂:序列标注与命名实体识别(NER)概述及标注方法解析

目录 一、序列标注&#xff08;Sequence Tagging&#xff09;二、命名实体识别&#xff08;Named Entity Recognition&#xff0c;NER&#xff09;**命名实体识别的作用****命名实体识别的常见实体类别** &#xff1a; 三、标签类型四、序列标注的三种常见方法1. **BIO&#xf…

01-Ubuntu24.04LTS上安装PGSQL

目录 一、准备工作 1.1、系统要求 1.2 、更新 Ubuntu 系统 1.3 、安装依赖 1.4 、添加 PostgreSQL 16 软件源 二、安装 PostgreSQL 16 数据库 三、管理 PostgreSQL 服务 四、PostgreSQL 管理操作 4.1 、访问 Postgres 超级用户账户 4.2 、创建数据库并设置管理权限 4…

利用阿里云镜像仓库和 Github Action 同步镜像

利用阿里云镜像仓库和 Github Action 同步镜像 由于某些未知原因,国内无法直接从 DockerHub 拉取镜像,在不使用 VPN 等违法工具的情况下,可以利用 GitHub 的 Action 流水线功能,将镜像推送到阿里云的个人镜像仓库中。 这种方式相较于其他方式虽然相对麻烦,但好在免费,且实…

iOS与Windows间传文件

想用数据线从 windows 手提电脑传文件入 iPhone&#xff0c;有点迂回。 参考 [1]&#xff0c;要在 windows 装 Apple Devices。装完、打开、插线之后会检测到手机&#xff0c;界面&#xff1a; 点左侧栏「文件」&#xff0c;不是就直接可以传&#xff0c;而是要通过某个应用传…

如何使用Python解析从淘宝API接口获取到的JSON数据?

基本的 JSON 解析 当从淘宝 API 接口获取到数据后&#xff08;假设数据存储在变量response_data中&#xff09;&#xff0c;首先要判断数据类型是否为 JSON。如果是&#xff0c;就可以使用 Python 内置的json模块进行解析。示例代码如下&#xff1a; import json # 假设respon…

Cesium K-means自动聚合点的原理

Cesium K-means自动聚合点的原理 Cesium 是一个开源的 JavaScript 库&#xff0c;用于在 Web 环境中创建 3D 地球和地图应用。它能够处理地理空间数据&#xff0c;并允许开发者对大规模的地理数据进行可视化展示。在一些应用中&#xff0c;尤其是当处理大量地理坐标点时&#…

入门数据结构JAVADS——如何构建一棵简单二叉排序树

目录 前言 什么是二叉排序树 二叉排序树的特点 二叉排序树示意图 构建二叉排序树 插入元素 搜索元素 删除元素 完整代码 结尾 前言 在整个十一月,笔者因为一些原因停笔了,但马上迈入12月进而进入2025年,笔者决定不再偷懒了,继续更新以促进学习的积极性.闲话说到这,今天…

【深度学习】四大图像分类网络之AlexNet

AlexNet是由Alex Krizhevsky、Ilya Sutskever&#xff08;均为Hinton的学生&#xff09;和Geoffrey Hinton&#xff08;被誉为”人工智能教父“&#xff0c;首先将反向传播用于多层神经网络&#xff09;在2012年ImageNet图像分类竞赛中提出的一种经典的卷积神经网络。AlexNet在…

基于 Python、OpenCV 和 PyQt5 的人脸识别上课打卡系统

大家好&#xff0c;我是Java徐师兄&#xff0c;今天为大家带来的是基于 Python、OpenCV 和 PyQt5 的人脸识别上课签到系统。该系统采用 Python 语言开发&#xff0c;开发过程中采用了OpenCV框架&#xff0c;Sqlite db 作为数据库&#xff0c;系统功能完善 &#xff0c;实用性强…

DevOps工程技术价值流:Jenkins驱动的持续集成与交付实践

一、Jenkins系统概述 Jenkins&#xff1a;开源CI/CD引擎的佼佼者 Jenkins&#xff0c;作为一款基于Java的开源持续集成&#xff08;CI&#xff09;与持续交付&#xff08;CD&#xff09;系统&#xff0c;凭借其强大的插件生态系统&#xff0c;成为DevOps实践中不可或缺的核心…

亚马逊开发视频人工智能模型,The Information 报道

根据《The Information》周三的报道&#xff0c;电子商务巨头亚马逊&#xff08;AMZN&#xff09;已开发出一种新的生成式人工智能&#xff08;AI&#xff09;&#xff0c;不仅能处理文本&#xff0c;还能处理图片和视频&#xff0c;从而减少对人工智能初创公司Anthropic的依赖…

mac下安装Ollama + Open WebUI + Llama3.1

本文介绍mac下安装Ollama Open WebUI Llama3.1 8b具体步骤。 目录 推荐配置Ollama Open WebUI Llama3.1简介安装Ollama安装Open WebUI 推荐配置 m1以上芯片&#xff0c;16g内存&#xff0c;20g以上硬盘空间 Ollama Open WebUI Llama3.1简介 Ollama: 下载&#xff0c;管理…