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类型的指针变量pint a = 10;                 //定义一个整型变量a,并给a负值10p = &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类型的指针变量pint a = 10;                     //定义一个整型变量a,并给a负值10p = &a;                         //将a的地址储存到变量p中(指针变量p指向了变量a)printf("赋值前\na: %d\n*p: %d\n",a,*p); //未赋值前a和*p都为10*p = 20;                        //将20赋值给*p.也就是将20赋值给aprintf("赋值后\na: %d\n*p: %d\n",a,*p); //可以看到a和*p都变成了20return 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,并初始化为 0x12345678int *p;              // 定义一个 int 类型的指针 pchar *q;             // 定义一个 char 类型的指针 qp = &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 函数中没有变化,仍然是 10printf("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 = 11a = a + 1;  // 在函数内部,c 被修改为原来 c 的值加 1,即 'A' 的 ASCII 值为 65,加 1 后变为 66,对应字符 'B'c = c + 1;// 打印修改后的 a 和 c 的值printf("a:%d\n", a);  // 输出 11,因为 a 被修改为 11printf("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 会被修改为 11printf("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);  // 11printf("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); // 声明功能函数5int 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);  // 调用传入的函数 freturn a * 2;
}int main() {// 定义函数指针 mm,指向 my_functionint (*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_functionvoid (*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_functionvoid (*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] = 3b = *sp+1;          //sp指向niu[0]    b = niu[0]+1 = 4c = *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] = 10f = *sp;            //sp指向niu[1]    f = niu[1] = 6x = 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;  // 结构体类型的别名 BOKint main()
{BOK bk1 ={"C Programming",    // title"Dennis Ritchie",   // author"001",              // id4.5,                // pop10,                 // stock39.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.mzph.cn/diannao/62689.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

单片机学习笔记 15. 串口通信(理论)

更多单片机学习笔记&#xff1a;单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…

树莓派5+文心一言 -> 智能音箱

一、简介 效果&#xff1a;运行起来后&#xff0c;可以连续对话 硬件&#xff1a;树莓派5、麦克风、音箱&#xff0c;成本500-1000 软件&#xff1a;snowboy作为唤醒词、百度语音作为语音识别、brain作为指令匹配、百度文心一言作为对话模块、微软的edge-tts语音合成... 二…

SAP SD学习笔记17 - 投诉处理3 - Credit/Debit Memo依赖,Credit/Debit Memo

上一章讲了 请求书&#xff08;发票&#xff09;的取消。 SAP SD学习笔记16 - 请求书的取消 - VF11-CSDN博客 再往上几章&#xff0c;讲了下图里面的返品传票&#xff1a; SAP SD学习笔记14 - 投诉处理1 - 返品处理&#xff08;退货处理&#xff09;的流程以及系统实操&#…

Linux服务器使用JupyterLab

一、JupyterLab的配置 1. conda配置 自行搜索conda安装与配置。 2. 环境创建 &#xff08;1&#xff09;创建环境 conda create -n jupyter python3.10&#xff08;2&#xff09;激活环境 conda activate jupyter&#xff08;3&#xff09;安装jupyter包 pip install -i…

Flutter:页面滚动

1、单一页面&#xff0c;没有列表没分页的&#xff0c;推荐使用&#xff1a;SingleChildScrollView() return Scaffold(backgroundColor: Color(0xffF6F6F6),body: SingleChildScrollView(child: _buildView()) );2、列表没分页&#xff0c;如购物车页&#xff0c;每个item之间…

使用GitZip for github插件下载git仓库中的单个文件

背景&#xff1a;git仓库不知道抽什么疯&#xff0c;下载不了单个文件&#xff0c;点击下载没有反应&#xff0c;遂找寻其他方法&#xff0c;在这里简单记录下。 使用GitZip for github插件下载仓库中的单个文件 1、首先在浏览器安装插件&#xff0c;并确保为打开状态。 2、然…

Unet改进57:在不同位置添加SFHF

本文内容:在不同位置添加CBAM注意力机制 论文简介 由于恶劣的大气条件或独特的降解机制,自然图像会遭受各种退化现象。这种多样性使得为各种恢复任务设计一个通用框架具有挑战性。现有的图像恢复方法没有探索不同退化现象之间的共性,而是侧重于在有限的恢复先验下对网络结构…

数据结构(初阶7)---七大排序法(堆排序,快速排序,归并排序,希尔排序,冒泡排序,选择排序,插入排序)(详解)

排序 1.插入排序2.希尔排序3.冒泡排序4.选择排序(双头排序优化版)5.堆排序6.快速排序1). 双指针法2).前后指针法3).非递归法 7.归并排序1).递归版本(递归的回退就是归并)2).非递归版本(迭代版本) 计算机执行的最多的操作之一就有排序&#xff0c;排序是一项极其重要的技能 接下…

DataWhale—PumpkinBook(TASK07支持向量机)

课程开源地址及相关视频链接&#xff1a;&#xff08;当然这里也希望大家支持一下正版西瓜书和南瓜书图书&#xff0c;支持文睿、秦州等等致力于开源生态建设的大佬✿✿ヽ(▽)ノ✿&#xff09; Datawhale-学用 AI,从此开始 【吃瓜教程】《机器学习公式详解》&#xff08;南瓜…

【Python数据分析五十个小案例】使用自然语言处理(NLP)技术分析 Twitter 情感

博客主页&#xff1a;小馒头学python 本文专栏: Python爬虫五十个小案例 专栏简介&#xff1a;分享五十个Python爬虫小案例 项目简介 什么是情感分析 情感分析&#xff08;Sentiment Analysis&#xff09;是文本分析的一部分&#xff0c;旨在识别文本中传递的情感信息&…

【数据结构与算法】排序算法(上)——插入排序与选择排序

文章目录 一、常见的排序算法二、插入排序2.1、直接插入排序2.2、希尔排序( 缩小增量排序 ) 三、选择排序3.1、直接选择排序3.2、堆排序3.2.1、堆排序的代码实现 一、常见的排序算法 常见排序算法中有四大排序算法&#xff0c;第一是插入排序&#xff0c;二是选择排序&#xff…

Educator头歌:离散数学 - 图论

第1关&#xff1a;图的概念 任务描述 本关任务&#xff1a;学习图的基本概念&#xff0c;完成相关练习。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a;图的概念。 图的概念 1.一个图G是一个有序三元组G<V,R,ϕ>&#xff0c;其中V是非空顶点集合&am…

oracle RAC各版本集群总结和常用命令汇总

oracle RAC学习 RAC介绍 RAC&#xff1a;高可用集群&#xff0c;负载均衡集群&#xff0c;高性能计算集群 RAC是⼀种⾼可⽤&#xff0c;⾼性能&#xff0c;负载均衡的share-everything的集群 8i:内存融合雏形 内存融合雏形&#xff08;Oracle Parallel Server&#xff09;…

数据资产管理是什么?为什么重要?核心组成部分(分类分级、登记追踪、质量管理、安全合规)、实施方法、未来趋势、战略意义

文章目录 一、引言&#xff1a;数据的新时代二、什么是数据资产管理&#xff1f;2.1 定义2.2 核心功能 三、为什么数据资产管理至关重要&#xff1f;3.1 面对的数据管理挑战 四、数据资产管理的核心组成部分4.1 数据分类与分级4.2 数据资产登记与追踪4.3 数据质量管理4.4 数据安…

C++高阶算法[汇总]

&#xff08;一&#xff09;高精度算法概述 高精度算法是指能够处理超出常规数据类型表示范围的数值的算法。在 C 中&#xff0c;标准数据类型通常有固定的位数和精度限制&#xff0c;而高精度算法可以解决大数运算、金融计算和科学计算等领域的问题。 &#xff08;二&#x…

springboot365高校疫情防控web系统(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;高校疫情防控的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为…

Electron实现打开子窗口加载vue路由指定的组件页面白屏

白屏有两种情况&#xff1a; Vue项目使用的history路由的话就会显示空白&#xff0c;加载不出来路由&#xff0c;也不能跳转路由 这种情况看我上一篇文章Electron vue3 打包之后不能跳转路由-CSDN博客 Electron中已经能正常加载页面跳转路由&#xff0c;但是创建子窗口加载子页…

智能探针技术:实现可视、可知、可诊的主动网络运维策略

网络维护的重要性 网络运维是确保网络系统稳定、高效、安全运行的关键活动。在当今这个高度依赖信息技术的时代&#xff0c;网络运维的重要性不仅体现在技术层面&#xff0c;更关乎到企业运营的方方面面。网络运维具有保障网络的稳定性、提升网络运维性能、降低企业运营成本等…

泷羽sec-shell脚本(全) 学习笔记

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

鸿蒙学习使用模拟器运行应用(开发篇)

文章目录 1、系统类型和运行环境要求2、创建模拟器3、启动和关闭模拟器4、安装应用程序包和上传文件QA:在Windows电脑上启动模拟器&#xff0c;提示未开启Hyper-V 1、系统类型和运行环境要求 Windows 10 企业版、专业版或教育版及以上&#xff0c;且操作系统版本不低于10.0.18…