C语言是一款强大的语言,也是一本比较简单容易上手的编程语言,但是C语言也有重点难点,那就是指针和链表,我一直不得其门而入,现在我想记录下所有我在学习指针和链表过程中的重点。
指针的含义
相信很多在学指针的你们应该都能在网上看到了一个说法,那就是C语言的指针其实也是属于变量,只不过这是一种特殊的变量,是用来保存变量地址的变量。
指针的用处
看下面的代码例子:
#include "pch.h" #include <stdio.h> void change(int x, int y) { int temp = x; x = y; y = temp; } int main() { int a = 3; int b = 6; change(a, b); printf("a=%d,b=%d\n", a, b); return 0; }
a和b通过change方法交换值,这个方法貌似没什么问题,然而输出的结果却是两个值并没有交换。
结果显示依然是a=3,b=6。为什么会这样呢,我是学C#出身的,更加不能理解结果会是这样,但是我忘记了,C语言是面向过程的语言,并不像C#是面向对象的语言。
C语言普通的函数参数是传值的参数,main函数中的a和b与函数change的参数x和y是不同的值,虽然a通过change函数传值,使得a和x的值相等,但是也仅仅是值相等,两个值的地址并不是同一个地址,所以,x的值修改了但最终a的值没有修改。
再看下面的例子:
#include <stdio.h> void change2(int *x, int *y) { int temp = *x; *x = *y; *y = temp; } int main() { int a = 3; int b = 6; change2(&a, &b); printf("a=%d,b=%d\n", a, b); return 0; }
将交换的方法的参数修改成指针后,a和b的值就进行了交换,这又是为什么呢?主要是因为指针变量其实是保存变量地址的变量,例子中change2函数的指针x和y,在main函数中调用,则是直接将a和b的地址传进去了,那么修改的值当然是直接修改a和b所在地址的值了,所以最终a和b的值也被修改了。
指针的声明和定义
指针声明的格式:[数据类型] *[变量名称];
如下例子:
int *p; // 声明一个 int 类型的指针 p int **p; // 声明一个指针 p ,该指针指向一个 int 类型的指针 int *arr[10]; // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针 int(*arr)[10]; // 声明一个数组指针,该指针指向一个 int 类型的一维数组
和普通的变量不一样,指针变量多了一个一元运算符“”,这个符号表示间接寻址或间接引用运算符。
p表示p是指向某个变量地址的指针变量,而直接用p变量则表示是这个变量的地址。
#include <stdio.h> int main() { int a = 3; int *p = &a; printf("*p=%d\n", *p); printf("p=%d\n", p); return 0; }
例子中可以看出,p表示是指向a的地址的值,p则表示a的地址。我们可以这样理解:p 是一个指针,保存着一个地址,该地址指向内存中的一个变量; p 则会访问这个地址所指向的变量。
指针的初始化
指针声明后,当然是要初始化指针,如果不初始化,那么我们并不知道我们定义的指针指向了哪个地址,这样就容易导致报错了,那么这里,我主要记录有两种指针的初始化方式。
第一种,通过指向已经初始化的变量的地址:
int a = 9; int *p = &a;
第二种,给指针分配动态内存地址,但是分配的地址在使用完毕后需要释放,否则会浪费cpu的资源的(这里的分配malloc和free函数都在stdlid.h头文件中):
int *q = (int *)malloc(sizeof(int)); free(q);
没有初始化的指针,我们不知道它指向哪里,它有可能指向一个非法的地址,那它就变成了一个非法指针,这样就会爆内存的错误;当然,如果运气好的话,它也有可能指向了一个合法的地址,但是这样更棘手,因为是合法的地址,程序能够正常运行,但是你可能修改了一个你不知道的合法地址内的值,而你本意并不是想修改这个地址的值。
所以,声明的指针必须初始化,即使我们不初始化,也要给一个默认值0或者NULL。
int *p = NULL; printf("*p指向的地址是%d\n", p);
设置指针是一个空指针,表示指针不指向任何地址,这样就能避免上述出现的两个问题,当然,设置成0也是一样,但是这样就需要转换数据类型,消耗性能,不建议。
下面是几种引用指针的方法:
int a[] = { 2,3,4,5,6 }; int *p; p = a; // 指向a数组的首地址 p = &a[0]; // 指向a数组的首地址(同p = a) *p = a[0]; // 指向a数组的首地址(同p = a)
结构体
从一周前学C语言到现在,我觉得结构体算是和C#里最相像的一部分了,C语言的结构体是能够声明在头文件中的,下面是定义结构体的各种方式。
struct student { char name[10]; int age; int weight; int height; };
这种方式是声明了一个名称叫student的结构体类型,我们可以直接定义多个该类型的结构体变量,如:
struct student stu; struct student stu1; struct student stu2; ...
除了以上的声明方法,我们还能通过直接定义结构体变量。
struct { char name[10]; int age; int weight; int height; }student1,student2;
这里就已经定义了student1和student2两个结构体变量,这里没有定义结构体类型,所以,只能定义这两个变量而不能再定义其他类型的了。当然我们也是可以在定义变量的同时把类型也定义了。
struct student{ char name[10]; int age; int weight; int height; }student1,student2;
我所知道的所有的定义方式我都记录下来了,至于怎么用,就要看开发的场景是怎么样的了。
链表
学完指针和结构体,接下来肯定要学习两者结合起来的链表,这是C语言的重点,也是难点。
其实链表就是结构体指针,但是在结构体内部又定义了本身类型的结构体指针类型。
下面是单链表的初始化:
typedef struct student { int stuId; char stuName[10]; float stuScore; char stuSex[2]; struct student *nextstu; }student_t;
下面是双链表的初始化:
typedef struct student { int stuId; char stuName[10]; float stuScore; char stuSex[2]; struct student *prestu; struct student *nextstu; }student_t;
单链表其实就是当前的结构体指针,内部也包含一个指针,指向下一个指针;而双链表其实就是当前的结构体指针,内部包含了两个指针,一个指向前一个指针,一个指向后一个指针,如图:
到这里,C语言基础我已经基本复习完毕了(大学的时候学过基础,但没用过,算是复习了吧),复习完当然得要动手做个小东西了,所以我模仿网上,写了一个学生信息管理系统,比较小,但是麻雀虽小,五脏俱全,下面是代码:
int selection; typedef struct student { int stuId; char stuName[10]; float stuScore; char stuSex[2]; struct student *nextstu; }student_t; student_t *stu_List; void initstulist(); void addstu(); void deletestu(); void searchstu(); void changestu(); void liststus();
#include <stdio.h> #include <stdlib.h> #include "CRUD.h" void initstulist() { stu_List = NULL; } void addstu() { // 初始化新建节点 student_t *stu = (student_t *)malloc(sizeof(student_t)); stu->nextstu = NULL; // 提示用户输入数据 printf("请输入学生信息\n学号:"); scanf("%d", stu->stuId); printf("姓名:"); scanf("%s", &(stu->stuName)); printf("总分:"); scanf("%f", &(stu->stuScore)); printf("性别:"); scanf("%s", &(stu->stuSex)); if (stu_List == NULL) { stu_List = stu; } else { student_t *prestu = stu_List; while (prestu->nextstu != NULL) { prestu = prestu->nextstu; } ; prestu->nextstu = stu; } liststus(); char iscontinue[2]; printf("是否继续添加?(y/n):"); scanf("%s", &iscontinue); if (iscontinue[0] == 'y') { addstu(); } } void deletestu() { liststus(); int stuId; printf("请在这里输入你要删除的学生序号:"); scanf("%d", &stuId); student_t *prestu = stu_List; student_t *stu = NULL; if (prestu->stuId == stuId) { stu_List = prestu->nextstu; return; } stu = stu_List->nextstu; while (stu != NULL) { if (stu->stuId == stuId) { prestu->nextstu = stu->nextstu; break; } prestu = prestu->nextstu; stu = stu->nextstu; } liststus(); } void searchstu() { int stuId; printf("请在这里输入你要查找的学生序号:"); scanf("%d", &stuId); student_t *stu = stu_List; while (stu != NULL) { if (stu->stuId == stuId) { break; } stu = stu->nextstu; } if (stu == NULL) { printf("没有查询到对应的结果,请检查是否输入错误?"); } else { printf("| 学号 | 姓名 | 总分 | 性别 |\n"); printf("| %d | %s | %f | %s |\n", stu->stuId, stu->stuName, stu->stuScore, stu->stuSex); } getchar(); getchar(); char iscontinue[2]; printf("是否继续查询?(y/n):"); scanf("%s", &iscontinue); if (iscontinue[0] == 'y') { searchstu(); } } void changestu() { liststus(); student_t *newstu = (student_t *)malloc(sizeof(student_t)); newstu->nextstu = NULL; // 提示用户输入数据 printf("请输入需要更改的学生信息\n学号:"); scanf("%d", &(newstu->stuId)); printf("姓名:"); scanf("%s", &(newstu->stuName)); printf("总分:"); scanf("%f", &(newstu->stuScore)); printf("性别:"); scanf("%s", &(newstu->stuSex)); student_t *prestu = stu_List; student_t *stu = NULL; if (prestu->stuId == newstu->stuId) { newstu->nextstu = prestu->nextstu; return; } stu = stu_List->nextstu; while (stu != NULL) { if (stu->stuId == newstu->stuId) { prestu->nextstu = newstu; newstu->nextstu = stu->nextstu; break; } prestu = prestu->nextstu; stu = stu->nextstu; } liststus(); char iscontinue[2]; printf("是否继续修改?(y/n):"); scanf("%s", &iscontinue); if (iscontinue[0] == 'y') { changestu(); } } void liststus() { printf("| 学号 | 姓名 | 总分 | 性别 |\n"); student_t *stu = stu_List; while (stu != NULL) { printf("| %d | %s | %f | %s |\n", stu->stuId, stu->stuName, stu->stuScore, stu->stuSex); stu = stu->nextstu; } getchar(); getchar(); }
#include <stdio.h> #include <stdlib.h> #include "CRUD.h" void viewMain() { while (1) { system("CLS"); printf("*******************************\n"); printf("1.增加一条学生信息。\n"); printf("2.删除一条学生信息。\n"); printf("3.查询一条学生信息。\n"); printf("4.修改一条学生信息。\n"); printf("5.列出学生信息表。\n"); printf("*******************************\n"); printf("请在这里输入你的选择(输入序号按回车即可):"); scanf("%d", &selection); switch (selection) { case 1: // 增 addstu(); break; case 2:// 删 deletestu(); break; case 3:// 查 searchstu(); break; case 4:// 改 changestu(); break; case 5:// 列数据 liststus(); break; default: break; } } } int main(int argc, char *argv[]) { initstulist(); viewMain(); return 0; }
在写代码的时候,我发现了几个我之前没有注意的问题:
1.在用scanf输入数据到结构体属性中时,依然需要用取地址符号;
printf("请输入需要更改的学生信息\n学号:"); scanf("%d", &(newstu->stuId)); printf("姓名:"); scanf("%s", &(newstu->stuName)); printf("总分:"); scanf("%f", &(newstu->stuScore)); printf("性别:"); scanf("%s", &(newstu->stuSex));
这让我有点措手不及,暂时没有找到好的办法,所以就直接这样写值了。
2.在我定义字符的时候,不能直接定义char类型,要定义char数组,否则会报堆栈内存错误,而且定义的char数组长度还要大于1,这让我有点摸不着头脑
char iscontinue[2]; printf("是否继续修改?(y/n):"); scanf("%s", &iscontinue); if (iscontinue[0] == 'y') { changestu(); }
下面我要去网上查找解决这两个问题的方法了,如果大家有好的建议希望能指出,我一定修改!!——来自一个菜鸟的心声