当前位置: 首页 > article >正文

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表中添加节点


http://www.kler.cn/a/418553.html

相关文章:

  • STM32的CAN波特率计算
  • Vue 3 的双向绑定原理
  • 4.5-Channel 和 Flow:SharedFlow 和 StateFlow
  • Scala关于成绩的常规操作
  • 详解高斯消元
  • 更多开源创新 挑战OpenAI-o1的模型出现和AI个体模拟突破
  • 深度解析MySQL的刷脏机制
  • 11. 名称空间
  • 深入解析 MySQL 启动方式:`systemctl` 与 `mysqld` 的对比与应用
  • 【iOS】《Effective Objective-C 2.0》阅读笔记(一)
  • 力扣103.二叉树的锯齿形层序遍历
  • git clone超大仓库时报错:fatal: early EOF
  • centos挂载ntfs或exFAT格式硬盘
  • 系统监控——分布式链路追踪系统
  • AJAX一、axios使用,url组成(协议,域名,资源路径)查询参数和化简,错误处理,请求/响应报文,状态码,接口文档,
  • 动态规划(c基础)
  • 【大数据学习 | Spark调优篇】Spark之内存调优
  • 深度学习基础3
  • 匿名发帖/匿名论坛功能设计与实现(编辑发帖部分)
  • 乌班图单机(不访问外网)部署docker和服务的方法
  • 【React】全局状态管理(Context, Reducer)
  • 在Window10或11系统中同时安装 JDK8 和 JDK11
  • 使用Docker Compose安装WordPress(ARM/x86架构)
  • 六、Python —— 函数
  • CondaValueError: Malformed version string ‘~‘: invalid character(s).
  • 猜一个0到10之间的数字 C#