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
", a, b);
	return 0;
}

a和b通过change方法交换值,这个方法貌似没什么问题,然而输出的结果却是两个值并没有交换。 Image
结果显示依然是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
", a, b);
	return 0;
}

Image
将交换的方法的参数修改成指针后,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
", *p);
	printf("p=%d
", p);
	return 0;
}

Image
例子中可以看出,*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
", 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;

单链表其实就是当前的结构体指针,内部也包含一个指针,指向下一个指针;而双链表其实就是当前的结构体指针,内部包含了两个指针,一个指向前一个指针,一个指向后一个指针,如图:
Image
Image
到这里,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("请输入学生信息
学号:");
	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("|    学号    |    姓名    |    总分    |    性别    |
");
		printf("| %d | %s | %f | %s |
", 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("请输入需要更改的学生信息
学号:");
	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("|    学号    |    姓名    |    总分    |    性别    |
");
	student_t *stu = stu_List;
	while (stu != NULL)
	{
		printf("| %d | %s | %f | %s |
", 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("*******************************
");
		printf("1.增加一条学生信息。
");
		printf("2.删除一条学生信息。
");
		printf("3.查询一条学生信息。
");
		printf("4.修改一条学生信息。
");
		printf("5.列出学生信息表。
");
		printf("*******************************
");
		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;
}

Image

在写代码的时候,我发现了几个我之前没有注意的问题:
1.在用scanf输入数据到结构体属性中时,依然需要用取地址符号;

printf("请输入需要更改的学生信息
学号:");
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();
}
下面我要去网上查找解决这两个问题的方法了,如果大家有好的建议希望能指出,我一定修改!!——来自一个菜鸟的心声
原文地址:https://www.cnblogs.com/ghm-777/p/11671391.html