目录
前言
上周完成了C基础的课程,接下来就进入了【Linux系统和C高级阶段】,这周主要是接着接着C基础部分的,讲了一些更深一点的东西:接着函数部分,讲了函数指针、指针函数、函数指针数组和指向函数指针数组的指针,其中前三个是比较重要的,后面两个就属于套娃了,了解能看懂即可;再就是讲了typedef关键字、分文件编程。期间还针对比较难懂的指针,做了针对性的复习;然后讲了存储类型、枚举(enum)、结构体和共用体。
因为周五,上午时间复习,下午时间是阶段性考试,所以这周四天的时间里,讲的东西还是挺多的,而且也都是重点,因此这次复习总结,主要会针对函数和指针和数组的结合、结构体等几个方向,进行重点复习。
同样,写此文章,是想以这种碎碎念的方式回顾重点、重复盲点、加深印象,复习、总结和反思本周的学习,仅供后期自己回顾使用。知识体系不完善,内容也不详细,仅供笔者复习使用。如果是有需要笔记,或者对这方面感兴趣,可以私信我,发你完整的知识体系和详细内容的笔记。如有任何错误请多指正,也欢迎友好交流,定会虚心听取大佬的宝贵意见!
周一
一、函数
1.1 指针函数
本质是一个函数,返回值是一个指针类型。(后面是什么,就是什么)
格式:
返回值类型 *函数名(形参1类型 形参1名, 形参2类型 形参2名 ){}
注意:
指针函数不能返回局部变量的地址。
可以返回:
1.全局变量的地址
2.可以返回 static 修饰的局部变量的地址
3.也可返回由参数传递过来的地址 (例如 :strcpy strcat 等)
1.2 函数指针
本质是一个指针,指向一个函数。
格式:
返回值类型 (*函数指针名)(函数的形参列表);
典型使用场景:
定义一个函数指针,指向一个函数,函数名就是函数的首地址,函数指针指向函数以后,就可以通过函数指针来调用函数了。
回调函数:
#include <stdio.h>
int my_add(int x, int y){
return x+y;
}
int my_sub(int x, int y){
return x-y;
}
//所谓的回调函数,是将函数指针最为某个函数的形参
//在函数中通过函数指针调用函数时,具体调用的是哪个函数
//取决与 jisuan 这个函数的调用者给他传递的第三个参数
//传的是什么,就回过头去调用什么 所以叫做 回调函数
int jisuan(int x, int y, int (*p)(int, int)){
int temp = p(x,y);
return temp;
}
int main(int argc, const char *argv[])
{
int a = 20;
int b = 10;
printf("jisuan(a, b, my_add) = %d\n", jisuan(a, b, my_add));//30
printf("jisuan(a, b, my_sub) = %d\n", jisuan(a, b, my_sub));//10
return 0;
}
1.3 函数指针数组(了解)
本质是一个数组,数组中的每个元素都是一个函数指针。
格式:
返回值类型 (*函数指针数组名[数组长度])(函数的形参表);
1.4 指向函数指针数组的指针(了解)
本质是一个指针,指向一个函数指针数组。
格式:
返回值类型 (*(*指针名))(函数的形参表);
二、typedef
2.1 typedef的使用
本质上是用来给类型起别名的。
可以这么理解:原本的名字很长很复杂,可以简化的起一个简称或者绰号,这个简称或者绰号和和它的大名代表的意思是一模一样的。
例如:
typedef unsigned int m;//m本来是变量名,加上typedef之后就变成了新的类型名
//使用m定义的变量 和 使用unsigned int定义的变量是一样的
unsigned int v1 = 0;
m v2 = 0;
2.2 typedef和define的区别
1.宏定义是在预处理阶段完成替换的,typedef是类型的重定义,会在编译阶段检查;
2.宏定义只是简单的替换(无脑替换),typedef是类型的重定义;
3.typedef 指定后面必须要加分号,而define 不强制要求;
三、分文件编程
实际开发的过程中,会根据函数的功能不同,按照模块,分成多个文件处理。
.c 叫做源文件 .h 叫做头文件
.c 放的是函数的定义 .h 中放的是函数的声明
编译时 需要 gcc 后面加上所有的 .c 文件
简单归纳理解:程序篇幅过大,为了方便数理逻辑,main.c里放的都是函数调用;其他.c文件中放的是功能实现,.h文件里放的是函数声明。
注意事项:
1、.c文件中调用自己的头文件要用引号,不在同一目录下,要指明头文件所在的目录;
2、防止头文件重复包含,一般要在.h文件中加如下代码:
#ifndef __FILI_H__ //防止头文件重复包含的 __FILI_H__
#define __FILI_H__ //注意下划线和大小写;
#endif
周二
今天老师根据我们的学习情况,做了针对指针的复习。
一、指针复习
使用指针的三步骤:
1.定义指针变量
2.明确指针的指向(这一步必不可少)
3.操作指针指向的空间
1.1 一级指针
char *p;
//*p = 'H'; //错误的写法!! 指针的指向还不明确呢
int *q;
char value = 'N';
p = &value;
p++; //正确的 指针的指向向后偏移 1个char
*p++; //相当于 取 *p 的值 然后 p++
(*p)++; //相当于 value++, p的指向没有变
++(*p); //相当于 ++value, p的指向没有变
1.2 二级指针
int a = 10;
int *p = &a;
int **q = &p;
q++; //让 q 保存 p 后面一个 int * 大小的空间的首地址
*q++; //相当于 取出 *q 的值(也就是p的值) 然后 q++
(*q)++; //相当于 p++
p++; //让 p 指向 a 后面一个int 大小的空间的首地址
*p++; //相当于 取 *p 的值(也就是a的值) 然后 p++
(*p)++; //相当于 a++
1.3 指针和一维数组
格式:
char arr[] = "hello world";
char *p = arr;
等价关系:
arr[i] <==> *(arr+i) <==> p[i] <==> *(p+i)
区别:
arr是常量 p是变量
练习:
char *p = "helloworld";
char a[] = "helloworld";
char *str;
char b[32];
p++; //正确的 p的指向后移一个char
*p++; //正确的 相当于 先取*p 然后p++
(*p)++; //错误的 常量区的内容不允许修改
*p = 'H'; //错误的 常量区的内容不允许修改
p = "hqyj"; //正确的 p的指向可以改变
a++; //错误的 a是数组名 是常量 不能++
*a++; //错误的 a不能++
(*a)++; //正确的 相当于 a[0]++
*a = 'H'; //正确的 相当于 a[0] = 'H';
a = "hqyj"; //错误的 a是数组名 是常量 不能被赋值
str++; //正确的 指针str的指向 向后偏移一个 char
*str = 'H'; //错误的 str是野指针 不能对不确定的空间赋值
str = p; //正确的 指针变量的相互赋值
*str = 'H'; //错误的 常量区的内容不允许修改
b = a; //错误的 b也是数组名 是常量 不能被赋值
//数组一旦定义好了就不能整体赋值了
1.4 指针数组
本质是一个数组,数组中每个元素都是一个指针。
char *s[4] = {"./a.out", "hello", "world", "beijing"};
//s是一个指针数组 每个元素都是一个 char * 类型的指针
//s[0]指向 "./a.out"
//s[1]指向 "hello"
//s[2]指向 "world"
//s[3]指向 "beijing"
printf("%s\n", s[1]);//hello
printf("%s\n", *(s+2));//world
printf("%c\n", *(*(s+3)+3));//'j'
printf("%c\n", s[3][3]); //'j'
1.5 数组指针
本质是一个指针,指向一个二维数组,也叫行指针。
int s[2][3] = {1,2,3,4,5,6};
int (*p)[3] = s;//定义了一个行指针p指向 二维数组s
等价关系:
s[i][j] <==> *(s[i]+j) <==> *(*(s+i)+j) <==
==> p[i][j] <==> *(p[i]+j) <==> *(*(p+i)+j)
区别:
s 是常量 p 是变量
p++; //p的操作空间是 3个int 所以p++ 相当于p偏移了 12个字节
*p+1; //*p 操作空间是 1个int 所以 *p+1 是 第0行第1个元素的地址
*(*p+1);//第0行第1个元素
二、总结
根据老师课上讲的内容,加上我在csdn上找的一位大神对指针的理解的文章,结合起来还是比较好理解的:
- int p; //这是一个普通的整型变量
- int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针
- int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组
- int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组
- int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针
- int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.
- int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据
- Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针
- int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.
结合优先级,对指针进行组合,相对来说还是比较好理解的。
周三
一、分配内存的方式
c语言的本质是操作内存。
1.1 由操作系统在栈区分配
定义变量的时候,操作系统会在栈区根据类型给变量分配对应大小的空间。
1.2 动态内存的分配与回收
手动分配和回收;
函数说明:
malloc()
#include <stdlib.h>
void *malloc(size_t size);
功能:在堆区手动分配内存空间
参数:size 要分配的空间的大小 单位:字节
返回值:分配的空间的首地址
free()
#include
void free(void *ptr);
功能:释放由malloc在堆区分配的空间
参数:ptr 要释放的空间的首地址
malloc分配的内存是在堆区,堆区的内存空间操作系统不会回收,需要程序员自己记得不用的时候要通过free函数回收资源,否则就会造成内存泄漏。
内存泄漏:内存空间只分配,不释放。
内存泄漏只发生在长时间运行的程序,如网络的服务器程序上,因为我们自己写的练习的小程序,./a.out 之后进程就直接结束了,进程结束,操作系统会回收进程的所有资源。
二、存储类型
2.1 const
const 修饰变量的时候,表示只读变量,不能通过变量名来修改变量的值。
例如:
const int a = 10;
a = 20;//错误的
const 修饰指针的时候
const int *p; //*p的内容不允许修改,p的指向可以修改
int const *p; //同上
int * const p; //p的指向不允许修改,*p的内容可以修改
const int * const p; //指针p指向和指向的内容都不允许修改
2.2 static
static 关键字主要有两个作用:
1. 延长局部变量的生命周期(详见例1)
2.限制作用域(例子见下面 extern 的例子)
注意:
1.在局部定义的变量都称之为局部变量,生命周期是最近的{}结束,生命周期结束时,
操作系统会回收变量占用的内存空间。
2.如果加了static关键字修饰,相当于修饰成静态变量;静态变量是存储在 data段 或者 bss段的,占用的内存空间是在main函数执行之前就分配的。
3.由static修饰的变量只能在当前文件中访问。
2.3 extern
作用:声明变量或者函数是在其他的文件中定义的
如果在一个.c文件中想使用另一个.c文件中的变量或者函数
需要在第一个.c中使用关键字 extern 来声明。
2.4 register
修饰的是一个寄存器类型的变量。
寄存器类型的变量,访问效率更高。
CPU访问数据的优先级 (寄存器--->高速缓存cache--->内存)
因为CPU寄存器的个数是有限的,有的是37个有的是40个...
我们不能把所有变量都定义成寄存器变量,----应用层开发register基本不使用
如果用到了,要注意:register修饰的变量是不能取地址的。
2.5 volatile
volatile关键字的作用是防止编译器优化。
要求每次取数据的时候都在内存上取,而不是取寄存器或者cache上的数据。
volatile的使用场景:
1.多线程访问同一个变量的时候
2.处理中断状态寄存器的时候
2.6 auto
声明的变量是一个自动类型变量
局部变量如果不加任何修饰默认的都是 auto
非自动类型的变量:
1.全局变量
2.static修饰的变量
三、枚举(enum)
3.1 概念
枚举是数据的有限罗列,枚举是一个基本类型。
枚举可以用来防止魔鬼数字。
3.2 定义格式
enum 枚举类型名{
成员1,
成员2,
成员3,
成员4 = 10,
成员5,
...
};
3.3 注意事项
1.枚举一旦定义好之后,枚举的成员都是常量
2.枚举的第一个成员如果没有初始值,默认值是 0
3.枚举成员的值是依次递增的,依次加1
4.枚举的成员之间用 逗号 分隔
5.如果给枚举的某个成员赋了初始值,后面成员的值在该值的基础上依次递增1
6.如果枚举的成员和局部变量重名时,采用局部优先原则
7.枚举类型的大小:一般情况下都是 4 ,如果枚举的成员有值超过4字节范围了 就是8
3.4 其他
其他关于枚举的定义、基本使用、和tytedef结合使用,基本上和结构体一样,类比结构体的使用即可。
四、结构体(struct)
4.1 概念
结构体是一个构造类型,里面可以是不同数据类型的集合。
结构体的每个成员在内存上是连续的(涉及到内存对齐的问题)。
4.2 格式
struct 结构体名{
数据类型1 成员1;
数据类型2 成员2;
...
数据类型n 成员n;
};
4.3 注意事项
注意和枚举区分!
1.结构体是一个构造类型
2.结构体的成员是变量
3.结构体的成员之间使用 分号 分隔
4.结构体变量之间可以直接相互赋值
4.4 定义格式
格式1:
struct 结构体名 变量名;
格式2:
struct 结构体名{
数据类型1 成员1;
数据类型2 成员2;
...
数据类型n 成员n;
}变量1,变量2;
格式3:
struct{
数据类型1 成员1;
数据类型2 成员2;
...
数据类型n 成员n;
}变量1,变量2;
4.5 结构体和typedef结合
格式1:
typedef struct 结构体名{
数据类型1 成员1;
数据类型2 成员2;
...
数据类型n 成员n;
}新的类型名;
struct 结构体名 变量名;
新的类型名 变量名;
格式2:
struct{
数据类型1 成员1;
数据类型2 成员2;
...
数据类型n 成员n;
}新的类型名;
4.6 结构体指针的格式
typedef struct 结构体名{
数据类型1 成员1;
数据类型2 成员2;
...
数据类型n 成员n;
}新的类型名;
struct 结构体名 *指针名;
新的类型名 *指针名;
4.7 结构体访问成员
struct Student{
char name[32];
int id;
int score;
int (*p)(int, int);//C语言中结构体里不能封装函数,但是可以有函数指针
};
变量版:
变量.成员
struct Student s1;
//s1.name
//s1.id
//s1.score
//s1.p
指针版:
指针->成员
struct Student *s2;
s2 = &s1;
//s2->name
//s2->id
//s2->score
//s2->p
周四
接前一天的结构体内容
一、结构体
1.1 结构体变量的定义及初始化
写法1:
struct Stu{
char name[32];
char sex;
int age;
};
struct Stu s1;
strcpy(s1.name, "zhangsan");
s1.sex = 'm';
s1.age = 30;
//指针必须明确指向了 才能操作
struct Stu *p1 = malloc(sizeof(struct Stu));
strcpy(p1->name, "lisi");
p1->sex = 'w';
p1->age = 30;
写法2:
struct Stu{
char name[32];
char sex;
int age;
};
struct Stu s1 = {"zhangsan", 'w', 20};
写法3:
struct Stu{
char name[32];
char sex;
int age;
}s1;
strcpy(s1.name, "zhangsan");
s1.sex = 'm';
s1.age = 30;
写法4:
struct Stu{
char name[32];
char sex;
int age;
}s1 = {"zhangsan", 'w', 20};
写法5:
struct Stu{
char name[32];
char sex;
int age;
};
struct Stu s1 = {
.name = "zhangsan",
.sex = 'm'
}
写法6:
struct Stu{
char name[32];
char sex;
int age;
};
struct Stu s1;
s1 = (struct Stu){"zhangsan", 'w', 20};
1.2 结构体数组的赋值和初始化
写法1:
struct Stu{
char name[32];
char sex;
int age;
};
struct Stu s[5];//定义了一个结构体数组
strcpy(s[0].name, "zhangsan");
s[0].sex = 'w';
s[0].age = 30;
strcpy(s[1].name, "lisi");
s[1].sex = 'm';
s[1].age = 20;
写法2:
struct Stu{
char name[32];
char sex;
int age;
};
struct Stu s[3] = {
{"zhangsan", 'w', 25},
{"lisi", 'm', 30},
{"wangwu", 'w', 18}
};
写法3:
struct Stu{
char name[32];
char sex;
int age;
};
struct Stu s[3] = {
[0] = {"zhangsan", 'w', 25},
[2] = {"lisi", 'm', 30}
};
写法4:
struct Stu{
char name[32];
char sex;
int age;
};
struct Stu s[3] = {
[0] = {
.name = "zhangsan",
.sex = 'w'},
[2] = {
.sex = 'm',
.age = 30}
};
1.3结构体中可以包含其他结构体的变量
#include <stdio.h>
struct AA{
int m;
};
struct BB{
int w;
struct AA aa;
//struct BB v;//错误的
struct BB *p;//正确的
};
int main(int argc, const char *argv[])
{
struct BB value;
value.w = 10;
value.aa.m = 30;
printf("%d %d\n", value.w, value.aa.m);//10 30
return 0;
}
1.4 结构体封装函数指针
结构体不允许放函数,但是可以放函数指针
#include <stdio.h>
#include <stdlib.h>
struct Test{
int value;
int (*func)(int, int);
};
int my_add(int x, int y){
return x+y;
}
int my_sub(int x, int y){
return x-y;
}
int main(int argc, const char *argv[])
{
struct Test t1;
t1.func = my_add;
struct Test *t2 = (struct Test *)malloc(sizeof(struct Test));
t2->func = my_sub;
printf("%d\n", t1.func(10, 20));//30
printf("%d\n", t2->func(10, 20));//-10
return 0;
}
1.5 结构体对齐(结构体所占的空间大小)
64位系统:按照最大的成员进行对齐。
最大是2字节就按2字节对齐,是4按4,8按8,16按16对齐。
32位系统:使用最多、面试笔试最常考
1.如果结构体的成员都小于4字节,则按照最大的成员进行对齐
2.如果有成员的大小大于等于4字节,则都按照4字节对齐
3.要注意char和short连续存储的问题!!!
64位系统将程序按32位编译 需要加编译选项 -m32
注意:结构体中嵌套结构体时的对齐规则
简单来说就是,结构体B中如果嵌套了结构体A,如果在创建A的阶段,产生了多余的空间,但结构体B也是没法用的;但如果结构体D嵌套结构体 C,因为C要对齐D而产生的多余的空间,D中的其他成员是可以占用的。
#include <stdio.h>
struct A{
int a;
char b;
};
struct B{
struct A m;
char c;
int d;
};//16 因为A的大小是8字节 虽然A的成员只占用了5字节
//但是为了满足A 自身的对齐 多分了3个字节
//这三个字节是没法被B中的成员占用的
struct C{
short a;
short b;
short c;
};
struct D{
struct C m;
char c;
int d;
};//12 C是6个字节 自身并没有多分配空间
//把C放在D中,为了满足D的对齐,相当于由D给C多分了2个字节
//多分的这2个字节 D中的成员可以占用
int main(int argc, const char *argv[])
{
printf("%d\n", sizeof(struct B));//16
printf("%d\n", sizeof(struct D));//12
return 0;
}
1.6 结构体位域
一种压缩结构体的手段
struct LED{
unsigned char led0:1; //本来char是8个bit 用冒号的方式限定成1个bit
unsigned char led1:1; //压缩之后每个成员能存储的数据范围也变小了
unsigned char led2:1;
unsigned char led3:1;
unsigned char led4:1;
unsigned char led5:1;
unsigned char led6:1;
unsigned char led7:1;
};
sizeof(struct LED) == 1
二、共用体(联合体)(union)
2.1 格式
union 共用体名{
类型1 成员1;
类型2 成员2;
...
类型n 成员n;
};
2.2 注意事项
1.共用体中所有的成员是共享同一块内存空间的
2.共用体的所有成员首地址是相同的
3.共用体的大小取决于成员中最大的那个
4.共用体的使用方式和结构体是一样的
注意:
因为共用体所有成员是共享同一块内存空间的,
修改一个成员 其他成员的值也会发生变化,所以要谨慎使用
使用共用体 判断自己主机是大端存储还是小端存储?
#include <stdio.h> union Test{ int a; char b; }; int main(int argc, const char *argv[]) { union Test t; t.a = 0x12345678; if(0x12 == t.b){ printf("大端\n"); }else if(0x78 == t.b){ printf("小端\n"); } return 0; }
周五
今天是准备考试的一天,上午复习了一会,因为教师节和中秋节,老师们为我们准备了月饼和一些小游戏(本社恐怕得要死),也为任课老师准备了小礼物,下午两个小时的时间,对我们这段时间的C语言成果进行了验收,虽然出了一点小插曲,但还是顺利完成了考试,然后老师用下午剩下的一点时间,讲了一下考试卷。
对于这次考试出现的问题,做如下总结:1、粗心大意,题目和选项都能看错;2、没摸清题目考点,精准掉入设计好的陷阱中;3、知识体系不完善,知识运用不熟练尤其是最近学的新知识。听能听懂,但真正用起来,容易出各种问题;4、程序题不熟练,不能很快速清晰思路,思路还需要慢慢整理。
对于这次考试的错题、难题和重点题作重点回顾:
12、在32位操作系统下,有下面的的结构体定义,则sizeof(struct Test)的值为( C )
struct Test{
char a; // a 占4字节
int b; // b 占4字节
char c;
short d; // c 和 d 占四字节
char e;
char f; // e 和 f 占四字节
};
A、10 B、12 C、16 D、14
考点:
32位系统的结构体对齐原则:
1.如果结构体的成员都小于4字节,则按照最大的成员进行对齐
2.如果有成员的大小大于等于4字节,则都按照4字节对齐
3.要注意char和short连续存储的问题!!!
补充64位系统的结构体对齐原则:
按照最大的成员进行对齐(不管是1字节还是2字节还是4字节、8字节甚至16字节,均按照它们中最大的字节对齐)。
17.下述程序第二次的输出结果为( B )。
int main(void)
{
extern int a;
int b=0;
static int c;
a+=3;
other();
b+=3;
other();
}
int a=5;
other()
{
int b=3;
static int c=2;
a+=5; b+=5; c+=5;
printf("%d,%d,%d\n",a,b,c);
c=b;
}
[A] 13,0,13 [B] 18,8,13 [C] 13,8,13 [D] 18,8,0
考点:全局变量、局部变量、static和extern关键字。
全局变量,生命周期和作用域是整个文件;
局部变量的作用域是它所被定义的函数,这个函数结束后空间就被系统收回;
static定义和初始化的变量,再次被static定义和初始化时会被跳过;
extern定义的变量,声明该变量在别处已经定义;
因此:
第一次打印的值为: 13,8,7
第二次打印的值为: 18,8,13
25.下面的程序运行结果为( C )。
char *RetMenory(void)
{
char p[] = “hello world”;
return p;
}
void Test(void)
{
char *str = NULL;
str = RetMemory();
puts(str);
}
A、语法有错误,不能编译 B、hello world C、运行会报错 D、hello world+乱码考点:函数的生命周期;
运行后,调用指针函数,返回值为一个地址,但函数调用后,内存释放,系统收回返回的地址的使用权,函数运行报错。
剩下的四个编程题,留作周末练习,重新整理一遍思路。
周末练习
练习1
1.编写函数实现冒泡排序。(要求:从终端获取十个数,按输入选择排序方式)。
函数名:void myBubbling(int *a, int len,int flag) a为数组名,len表示数组元素的个数,flag表示排序方式(0为升序,1为降序)。
#include <stdio.h>
void bubbling_sort(int *p,int len,int flag){
int temp = 0;
if(0 == flag){
for(int i = 0;i<len-1;i++){
for(int j = 0;j<len-1-i;j++){
if(p[j]>p[j+1]){
temp = p[j];
p[j] = p[j+1];
p[j+1] = temp;
}
}
}
}
if(1 == flag){
for(int i = 0;i<len-1;i++){
for(int j = 0;j<len-1-i;j++){
if(p[j]<p[j+1]){
temp = p[j];
p[j] = p[j+1];
p[j+1] = temp;
}
}
}
}
}
int main(int argc, const char *argv[])
{
int s[10] = {0};
int len = sizeof(s)/sizeof(s[0]);
int flag = 0;
int i = 0;
printf("请随机输入10个正整数:\n");
for(i = 0;i<len;i++){
scanf("%d",&s[i]);
}
printf("请选择排序方式(0为升序,1为降序):\n");
scanf("%d",&flag);
bubbling_sort(s,len,flag);
for(i = 0;i<len;i++){
printf("%d ",s[i]);
}
putchar(10);
return 0;
}
练习2
2.请实现类似atoi函数,把字符串“123456”转换成数值123456 ,并返回数值
函数名: int myatoi(char *str);
#include <stdio.h>
int myatoi(char *str){
int ret = 0;
while(*str){
ret = ret*10+(*str-'0');
str++;
}
return ret;
}
int main(int argc, const char *argv[])
{
char s[32] = "123456";
int a = 0;
a = myatoi(s);
printf("字符串 %s 转换成数值为 %d\n",s,a);
return 0;
}
练习3
3.下面findmax函数将计算数组中的最大元素及其下标值,请编写该函数。
函数名:void findmax ( int s[ ], int t, int *k );
#include <stdio.h>
void findmax(int s[],int t,int *k){
int i = 0;
int max = 0;
for(i = 1;i<t;i++){
if(s[i]>s[max]){
max = i;
}
}
*k = max;
}
int main(int argc, const char *argv[])
{
int s[5] = {1,22,2,33,8};
int max_index = 0;
findmax(s,5,&max_index);
printf("数组的最大值的下标是%d,最大值是%d\n",max_index,s[max_index]);
return 0;
}
练习4
4.在堆区创建一个结构体数组,数组名为student,成员包含姓名,学号,成绩(数据类型自己设定)。写一个程序,要求可以循环的从终端输入学生信息,当输入一行‘#’时表示完成输入。完成输入后自动打印出所输入的学生信息,打印结果按学生成绩从低到高打印。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define N 40 //班级能容纳的总人数
//每个学员的信息
typedef struct _Student{
int id;
char name[32];
int score;
}stu_t;
//整个班级的信息
typedef struct _Class{
stu_t person[N];
int count;//记录当前班级已有的人数
}class_t;
//函数声明
int create_class(class_t **);
int insert_student(class_t *);
int print_student(class_t *);
int sort_student(class_t *);
//创建班级结构体的函数
int create_class(class_t **p){
if(NULL == p){
printf("入参为NULL, 请检查\n");
return -1;
}
//*p = malloc(sizeof(*p));//错误的 这样写只有指针的大小
*p = (class_t *)malloc(sizeof(class_t));
if(NULL == *p){
printf("内存分配失败\n");
return -1;
}
//新分配的空间 清0
memset(*p, 0, sizeof(class_t));
return 0;
}
//添加新学员的函数
int insert_student(class_t *my_class){
//先对指针做非空检查
if(NULL == my_class){
printf("入参为NULL, 请检查\n");
return -1;
}
LOOP:
printf("请输入新学员的 (学号 姓名 成绩):");
scanf("%d%s%d", &(my_class->person[my_class->count].id),\
my_class->person[my_class->count].name,\
&(my_class->person[my_class->count].score));
my_class->count++;
if(!strncmp(my_class->person[my_class->count-1].name, "##",2)){
my_class->count--;
return 0;
}else{
printf("新学员 %s 的信息添加成功\n", my_class->person[my_class->count - 1].name);
goto LOOP;
}
}
//学员成绩排序 --降序
int sort_student(class_t *my_class){
//先对指针做非空检查
if(NULL == my_class){
printf("入参为NULL, 请检查\n");
return -1;
}
//使用冒泡排序 即可
int i = 0;
int j = 0;
stu_t temp;
for(i = 0; i < my_class->count-1; i++){
for(j = 0; j < my_class->count-1-i; j++){
if(my_class->person[j].score < my_class->person[j+1].score){
temp = my_class->person[j];
my_class->person[j] = my_class->person[j+1];
my_class->person[j+1] = temp;
}
}
}
printf("学员成绩降序排序完成\n");
return 0;
}
int print_student(class_t *my_class){
//先对指针做非空检查
if(NULL == my_class){
printf("入参为NULL, 请检查\n");
return -1;
}
int i = 0;
for(i = 0; i < my_class->count; i++){
printf("学号:%-5d 姓名:%-10s 成绩:%-3d\n", my_class->person[i].id,\
my_class->person[i].name,\
my_class->person[i].score);
}
return 0;
}
int main(int argc, const char *argv[])
{
class_t *my_class = NULL;
create_class(&my_class);
insert_student(my_class);
sort_student(my_class);
print_student(my_class);
return 0;
}
反思与总结
这个星期结束了C语言的课程,中秋节放假回来之后就要开始数据结构的课程了,总结这段时间的学习,可以说是稳中带紧,老师对进度的把握很厉害,对重点难点下的功夫很多,跟着老师的节奏我们学起来也很顺利,虽说课程上完了,但对C语言的学习还是要下功夫,这段时间暴露出自己一个比较大的问题,就是逻辑方面,找不到最佳的解决办法,或者是绕了一大圈去解决一个小问题;针对这个问题老师也一直在强调,平时做的例题,不仅仅是例题,更是通用的解决问题的方法,因此还是要经常回顾,继续提高自己解决问题的能力。
写在最后:写这篇文章是笔者一边看老师的目录,一边回想老师讲的内容,仅仅选取了我自己认为比较重要的,或者自己之前没接触过的进行汇总、总结,知识体系不完善,内容也不详细,仅供笔者复习使用。如果是有需要笔记,或者对这方面感兴趣,可以私信我,发你完整的知识体系和详细内容的笔记。写的仓促、水平有限,如有任何错误请多指正,也欢迎友好交流,定会虚心听取大佬的宝贵意见!