• 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();
    }
    
    下面我要去网上查找解决这两个问题的方法了,如果大家有好的建议希望能指出,我一定修改!!——来自一个菜鸟的心声
  • 相关阅读:
    Use Prerender to improve AngularJS SEO
    Prerender.io
    Prerender Application Level Middleware
    Prerender Application Level Middleware
    正则获取html标签字符串中图片地址
    xml转json
    videojs实现双击视频全屏播放、播放器全屏时视频未全屏
    自己编写jquery插件
    点击回退时需要点击2次才可返回js
    if中有逗号的写法
  • 原文地址:https://www.cnblogs.com/ghm-777/p/11671391.html
Copyright © 2020-2023  润新知