C语言指针
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();
}
下面我要去网上查找解决这两个问题的方法了,如果大家有好的建议希望能指出,我一定修改!!——来自一个菜鸟的心声