P 141 返回指针的函数 2023/2/16
一、基本介绍
C语言 允许函数的返回值是一个指针(地址),这样的函数称为指针函数。
二、入门案例
案例:请编写一个函数 strlong(),返回两个字符串中较长的一个。
#include<stdio.h>
#include<string.h>char *strlong(char *str1, char *str2){ //函数返回的char * (指针)printf("\nstr1的长度%d str2的长度%d", strlen(str1), strlen(str2));if(strlen(str1) >= strlen(str2)){return str1;}else{return str2;}
}int main(){char str1[30], str2[30], *str; // str是一个指针类型,指向一个字符串 printf("\n请输入第1个字符串");gets(str1); // 输入字符或者字符串printf("\n请输入第2个字符串");gets(str2);str = strlong(str1, str2);printf("\nLonger string: %s \n", str);getchar();return 0;
}
三、指针函数注意事项和细节
-
用指针作为函数返回值时需要注意,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针不能指向这些数据【案例演示】。
-
函数运行结束后会销毁该函数所有的局部数据,这里所谓的销毁并不是将局部数据所占用的内存全部清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存。
#include<stdio.h>int *func(){int n = 100; // 局部变量;在func返回时就会销毁return &n;
}int main(){int *p = func(); // func返回指针int n;// printf("okokok~"); //但是这里如果在赋值之前有其他的语句执行也会占用空间,这里面原先int n =100可能就会发生改变n = *p;// 思考:是否能够输出100?不一定// 依然输出了100,因为将100赋值给n了,直接就输出了,前面没有占用之前int n = 100的空间printf("\nvalue = %d\n", n); getchar();return 0;
}
- C 语言不支持在调用函数时返回局部变量的地址,如果确实有这样的需求,需要定义局部变量为 static 变量 【案例演示】
#include<stdio.h>int *func(){//int n = 100; // 局部变量;在func返回时就会销毁static int n = 100;// 如果这个局部变量是static性质,那么n存放数据的空间在 静态数据区(前面C程序布局图)// 静态数据区是不会被其他的变量来占用了return &n;
}// 下面代码同上!
四、案例练习
案例一:编写一个函数,它会生成 10 个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回它们。
#include<stdio.h>
#include<stdlib.h>
// 编写一个函数,返回一个一维数组
int * f1(){static int arr[10]; // 必须加上static,让arr的空间在静态数据区分配int i = 0;for(i = 0; i < 10; i++){ // 数组初始化赋值arr[i] = rand();}return arr;
}void main(){int *p;int i;p = f1(); // p指向f1中生成的数组首地址(即第一个元素的地址) for(i = 0; i < 10; i++){printf("\n%d",*(p+i));}getchar();
}
P 142 函数指针和内存布局 2023/2/26
一、基本介绍
1、一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。
(可以比作将函数名看成一个首地址,将地址付给函数指针)。
2、把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。
二、函数指针定义
returnType (*pointerName)(param list);
-
returnType
为函数返回值类型 -
pointerName
为指针名称 -
param list
为函数参数列表 -
参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称
-
注意( )的优先级高于*,第一个括号不能省略,如果写作
returnType *pointerName(param list);
就成了函数原型,它表明函数的返回值类型为returnType *
三、案例演示
#include<stdio.h>// 说明:
// 1.max函数
// 2.接收两个int,返回较大数
int max(int a, int b){return a>b ? a : b;
}
//int max(int a, int b);
int main(){int x, y, maxVal;//说明: 函数指针// 1. 函数指针名字pmax// 2. int表明该函数指针指向的函数是返回int类型// 3. (int,int)表示该函数指针指向的函数形参是接收两个int// 4. 在定义函数指针时,也可以写上形参名int (*pmax)(int a, int b) = max; int (*pmax)(int, int) = max; // *pmax就相当于找到了函数,然后传入实参printf("Input two numbers:");scanf("%d %d", &x, &y);// (*pmax)(x, y) 通过函数指针去调用函数maxVal = (*pmax)(x, y);// 当然指针有存放的地址和本身的地址:可输出查看,printf("Max value: %d\n pmax=%p pmac本身地址%p %p", maxVal,pmax,&pmax);getchar();getchar();return 0;
}
四、内存布局图
P 143 回调函数 2023/2/26
一、基本介绍
- 函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
- 简单的讲:回调函数是由别人的函数执行时调用你传入的函数(通过函数指针完成)。
二、应用实例
#include <stdlib.h>
#include<stdio.h>
// 回调函数
// 1. int (*f)(void)
// 2.f 就是函数指针,它可以接收函数是返回(int,没有形参的函数)
// 3.f 在这里被intArray调用,充当了回调函数的角色
void initArray(int *array, int arraySize, int (*f)(void)) { // 对地址进行操作就是修改了main里面的array
// 下面传了一个函数名给函数指针,然后通过函数指针调用getNextRandomValueint i ;for ( i=0; i<arraySize; i++){array[i] = f(); // 通过函数指针调用了 getNextRandomValue} // 调用方式2:(*f)()
}// 获取随机值
int getNextRandomValue(void) {return rand(); // rand系统函数,会返回一个随机数
}int main(void) {int myarray[10],i; // 定义了一个数组和int类型// 说明:// 1.调用intArray函数// 2.传入了一个函数名,getNextRandomValue(地址),需要使用函数指针接收initArray(myarray, 10, getNextRandomValue);for(i = 0; i < 10; i++) {printf("%d ", myarray[i]);}printf("\n");getchar();return 0;
}
P 144 空指针的使用 2023/3/11
一、指针注意的事项和细节
- 指针变量存放的是地址,从这个角度看指针的本质就是地址。(能够使用到地址的地方都可以使用指针)
- 变量声明的时候,如果没有确切的地址赋值,为指针变量赋一个 NULL 值是好的编程习惯。
- 赋为 NULL 值的指针被称为空指针,NULL 指针是一个定义在标准库 <stdio.h>中的值为零的常量 #define NULL 0 [案例]
#include<stdio.h>void main(){int *p = NULL; // 空指针;给空值是一个好习惯// 空指针就是一个为0的常量int num =34; p = # printf("*p=%d",*p); // 34getchar();
}
P 145 动态内存分配机制和案例 2023/3/12
一、动态内存分配
- C程序中,不同数据在内存中分配说明
- 全局变量——内存中的静态存储区。
- 非静态的局部变量——内存中的动态存储区——stack 栈。
- 临时使用的数据—建立动态内存分配区域,需要时随时开辟,不需要时及时释放——heap 堆。
- 根据需要向系统申请所需大小的空间,由于未在声明部分定义其为变量或者数组,不能通过变量名或者数组名来引用这些数据只能通过指针来引用)。
二、内存动态分配的相关函数
-
头文件 #Include <stdlib.h> 声明了四个关于内存动态分配的函数。
-
函数原型 :
void * malloc(usigned int size)
//memory allocation(内存分配)- 作用——在内存的动态存储区(堆区)中分配一个长度为size的连续空间。
- 形参size的类型为无符号整型,函数返回值是所分配区域的第一个字节的地址,即此函数是一个指针型函数,返回的指针指向该分配域的开头位置。
- malloc(100); 开辟100字节的临时空间,返回值为其第一个字节的地址。
-
函数原型:
void *calloc(unsigned n,unsigned size)
- 作用——在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组。
- 用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。
- 函数返回指向所分配域的起始位置的指针;分配不成功,返回NULL。
- p = calloc(50, 4); //开辟 50*4 个字节临时空间,把起始地址分配给指针变量 p。
-
函数原型:
void free(void *p)
- 作用——释放变量p所指向的动态空间,使这部分空间能重新被其他变量使用。
- p是最近一次调用calloc或malloc函数时的函数返回值。
- free函数无返回值。
- free§; // 释放p 所指向的已分配的动态空间。
-
函数原型:
void *realloc(void *p,unsigned int size)
- 作用——重新分配malloc或calloc函数获得的动态空间大小,将p指向的动态空间大小改变为size,p的值不变,分配失败返回NULL。
- realloc(p, 50); // 将 p 所指向的已分配的动态空间 改为50字节。
-
返回类型说明:
三、void指针类型
四、案例说明
#include<stdio.h>#include <stdlib.h>
int main() {void check(int *); //函数声明int * p,i;p = (int *)malloc(5*sizeof(int));// 5*4 开辟20字节的临时空间,(void*)强制转成(int*),赋给p;5个int类型空间for( i = 0; i < 5; i++){scanf("%d", p + i); // 数据就会填入到堆区里面去}check(p); // 函数调用free(p); // 销毁堆区p指向的空间;相当于被回收了,回收后是不能被使用的getchar();getchar();return 0;
}void check(int *p) {int i;printf("\n不及格的成绩 有: ");for(i =0; i < 5; i++){if(p[i] < 60) {
// *(p+1) 也是取值,p[i]则是取数组的元素,p[下标]取相应的值printf(" %d ", p[i]);}}
}
五、内存布局图
P 146 动态内存分配注意事项 2023/3/14
一、动态分配内存的基本原则
- 避免分配大量的小内存块。分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大内存块的系统开销大。
- 仅在需要时分配内存。只要使用完堆上的内存块,就需要及时释放它(如果使用动态分配内存,需要遵守原则:谁分配,谁释放), 否则可能出现内存泄漏。
free(p); // 销毁堆区p指向的空间;相当于被回收了,回收后是不能被使用的// 如果我们使用完后不释放,这块内存会一直存放在我们的堆区,下面函数也不使用和释放,一直存在,这就相当于内存泄漏。
- 总是确保释放以分配的内存。在编写分配内存的代码时,就要确定在代码的什么地方释放内存。
- 在释放内存之前,确保不会无意中覆盖堆上已分配的内存地址,否则程序就会出现内存泄漏。在循环中分配内存时,要特别小心
二、指针使用一览总结
P 147 为什么需要结构体 2023/3/14
一、结构体引入
引入:张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫
的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。
-
传统技术解决
- 单独定义变量解决(需要定义多个变量,猫的颜色,年龄,名字)
- 我们学习了数组,它是一组具有相同类型的数据的集合。在编程中,我们往往还需要一组类型不同的数据,例如猫的名字使用字符串、年龄是int,因为数据类型不同,不能用一个数组来存放。
-
现有技术缺点分析:不利于数据的管理和维护,因为本身猫的三个属性,是一个整体,传统方法是将其分解。
// 定义变量
char cat1Name[10] = "小白";
int cat1Age = 3;
char cat1Color[10] = "白色";// 使用数组
char *catsName[2] = {
"小白",
"小花"
}
char *catAge[2] = {1,2};
// 我们还要考虑第一只猫通过下标为0来取,都是非常的麻烦
P 148 结构体快速入门 2023/3/14
一、结构体入门
- 结构体与及结构体变量关系示意图
二、猫猫问题结构体解决
#include<stdio.h>
void main(){
/*张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。分析:1. 猫猫有三个成员(变量 )组成2. 使用结构体解决
*/// 创建结构体Cat【是数据类型】struct Cat{ // 结构体名Cat,Cat就是我们自己构造的数据类型char * name; // 名字,使用指针,指向一个字符串int age;char * color;};// 使用Cat结构创建对应的,变量struct Cat cat1; // cat1就是struct Cat的一个变量 int numstruct Cat cat2; // cat2就是struct Cat的一个变量 int num// 给 cat1 里面的各个成员赋值cat1.name = "小白";cat1.age = 3;cat1.color = "白色";// 给 cat2 里面的各个成员赋值cat2.name = "小花";cat2.age = 100;cat2.color = "花色";// 输出两只猫的信息;会发现这样更易于管理,将猫的属性管理在一起printf("\n 第一只猫 name=%s age=%d color=%s",cat1.name,cat1.age,cat1.color);printf("\n 第二只猫 name=%s age=%d color=%s",cat2.name,cat2.age,cat2.color);
}
P 149 结构体变量内存布局 2023/3/14
一、结构体和结构体变量的区别和联系
通过上面的案例和讲解我们可以看出:
- 结构体是自定义的数据类型,表示的是一种数据类型。
- 结构体变量代表一个具体变量,好比:
int num1 ; // int 是数据类型, 而num1 是一个具体的int变量struct Cat cat1; // Cat 是结构体数据类型, 而cat1 是一个Cat变量
3.Cat 就像一个“模板”,定义出来的结构体变量都含有相同的成员。也可以将结构体比作“图纸”,将结构体变量比作“零件”,根据同一张图纸生产出来的零件的特性都是一样的。
二、结构体在内存的布局
P 150 结构体成员 2023/3/14
一、如何声明结构体
语法:
struct 结构体名称 { // 结构体名首字母大写,比如Cat, Person
成员列表;
};// 举例:
struct Student{char *name; //姓名int num; //学号int age; //年龄char group; //所在学习小组float score; //成绩//成员也可以是结构体
};
二、成员基本介绍
-
从叫法上看:有些书上称为成员, 有些书说 结构体包含的变量。
-
成员是结构体的一个组成部分,一般是基本数据类型、也可以是数组、指针、
结构体等 。比如我们前面定义Cat结构体 的 int age 就是一个成员。
三、注意事项和细节说明
- 成员声明语法同变量,示例: 数据类型成员名。
- 字段的类型可以为:基本类型、数组或指针、结构体等。
- 在创建一个结构体变量后,需要给成员赋值,如果没有赋值(初始化)就使用可能导致程序异常终止。[ 案例演示 ]:
#include<stdio.h>
void main() {struct Cat{ // 结构体名字建议首写字母大写char *name; //名字 , 这里需要使用指针类型int age; //年龄char *color; // 颜色}; struct Cat cat1; // 没有赋值就可以以上变量指向的全是一些垃圾值,导致程序异常终止printf("\n名字=%s 年龄=%d 颜色=%s ", cat1.name, cat1.age, cat.color);getchar();
}
- 不同结构体变量的成员是独立,互不影响,一个结构体变量的成员 更改,不影响另外一个。[案例演示+图(Monster)]
// cat1和cat2是独立的,互不影响struct Cat cat1; // cat1就是struct Cat的一个变量 int numstruct Cat cat2; // cat2就是struct Cat的一个变量 int num// 给 cat1 里面的各个成员赋值cat1.name = "小白";cat1.age = 3;cat1.color = "白色";// 给 cat2 里面的各个成员赋值cat2.name = "小花";cat2.age = 100;cat2.color = "花色";
内存布局图: