前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >开发成长之路(3)-- C语言从入门到开发(讲明白指针和引用,链表很难吗?)

开发成长之路(3)-- C语言从入门到开发(讲明白指针和引用,链表很难吗?)

作者头像
看、未来
发布2021-09-18 10:50:37
5750
发布2021-09-18 10:50:37
举报
文章被收录于专栏:CSDN搜“看,未来”
在这里插入图片描述
在这里插入图片描述

文章目录

指针和动态内存分配

指针是C语言的基本概念,C语言中指针无处不在。实际上,每种数据类型,都有相应的指向T的指针类型。 指针类型变量存放的值,实际上就是内存地址。指针类型有两个最基本的操作:

代码语言:javascript
复制
&:取地址操作 
*:去引用 (间接引用)操作

引用&

首先,&不是地址运算符,而是类型标识符的一种,就像*也不是指针运算符一样。

就像char* 意为指向char的指针一样,int& 意为指向int 的引用。

栗子来一颗:

代码语言:javascript
复制
int a;
int &at = a;
//上述声明允许将at和a互换,它们指向相同的值和内存单元,就像连体婴一样。

上面这个栗子其实很有内涵在里面 我为什么不写成下面这个形式呢?

代码语言:javascript
复制
int a;
int &at;
at = a;

在指针中是可以的,但是&不允许,&必须在声明时将其初始化。

引用经常被用作函数参数,使得函数中的变量名成为调用程序中变量的别名。这种调用方法我一直搞得晕晕的,正好这次一次性根除。这种传递参数的方法称为按引用传递。按引用传递允许被调用函数能够访问调用函数中的变量。这是C++相比C的一个超越。 来个经典的栗子:

代码语言:javascript
复制
void swap_a(int &a,int &b)
{
	int temp;
	temp = a;
	a = b;
	b = temp;
}

//顺便来个指针的
void swap_b(int *a,int *b)
{
	int temp;
	temp = *a;	//a,b是指针,*a,*b才是int
	*a = *b;
	*b = temp;
}

int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int d = 4;
	swap_a(a,b);	//看仔细咯,这个是引用调用
	swap_b(&a,&b);	//看仔细咯,这个是指针调用
	//如果理解不了,这样理解:参数中的*和&只是走个过场,告诉人家那个参数是什么类型的
	//调用函数时的参数是a,不是*a,也不是&a
	//所以&a传的这个a是一个int类型,而*a的这个a就是指针,地址,所以要取地址传给它
	//虽然我语文不好,但是都讲到这份上了那应该是可以理解了
	return 0;
}

如果你的意图是让函数使用传给它的信息,又不想把这些信息进行改动,那么应该使用const。 将引用参数声明为const数据的好处有这些:

代码语言:javascript
复制
防止无意中被修改。
使用const参数可以兼容非const传参。
将引用用于结构

C++引入引用主要就是为了和结构和类。 它还通过让函数返回指向结构的引用而增添了一个有趣的特点,这与返回结构有所不同。

代码语言:javascript
复制
//代码太长,放段伪代码吧
struct Str	//不知道什么是结构体不急,稍后就会有
{
};

Str& test(Str &a,const Str &b)	
{
	//从b中取值,对a进行填充
	return a;//其实可以做void类型,没必要多此一举
}

int main()
{
	Str a,b,c;
	//b是有初值的,这是伪代码
	c = test(a,b);
	return 0;
}

如果test函数返回一个结构,而不是指向结构的引用,相当于把整个结构体复制到一个临时位置,再将这个拷贝复制给c,但是现在返回值为引用,将直接将a复制到c,效率更高。

返回引用时最重要的一点是:应避免返回函数终止时将不再存在的内存单元的引用。 下面是一个反面教材:

代码语言:javascript
复制
Str& test(const Str &d)
{
	Str &e;
	···
	return e;
}
何时使用引用参数?
代码语言:javascript
复制
程序员能够修改调用函数中的数据对象。
通过传递引用而不是整个数据对象,可以提高程序的运行速度。

指针

指针和const

将const用于指针有一些很微妙的地方。 可以用两种不同的方式将const关键字用于指针。

代码语言:javascript
复制
int age = 20; const int * pt = &age;
//该声明指出,pt指向一个const int,因此不能使用pt来修改这个值。
//现在来看一个很微妙的问题:其实age并不是一个常量,只是对于pt来说,它是一个常量。
//就是说age可以改,只不过不能用pt来改而已。

注意点:不允许将常量数据赋值给非常量指针,个中理由就不用多解释了吧。

代码语言:javascript
复制
const int age = 20; int * pt = &age;

int sloth = 80; int * const finger = &sloth;
// 这种声明格式使得这个指针只能指向sloth,不过可以通过这个指针修改sloth的值。
通过指针返回字符串的函数

现在,假设需要一个返回字符串的函数,是的,函数无法返回一个字符串,但是可以返回字符串的地址,这样效率更高。

代码语言:javascript
复制
void test(char *rc)
{
	···
	memset(rc,字符串);
	···
}

相当于是使用回调函数,我个人比较喜欢这一套模式。

通过指针返回结构

具体操作参考第二点。 当然,这里还有另外的应用场景:

代码语言:javascript
复制
void test2(const JieGouTi1 *a,JieGouTi2 *b)
{
	//将a中的某些值赋值给b
}
//这里有一个注意点,传进去赋值的结构体指针最好用const.
函数指针

关于为什么要使用函数指针,我的理解还不是很深刻,毕竟功力不足。但是我知道那些回调函数都是用函数指针的,所以对函数指针必须要理解好。 这叫啥,“但行好事,莫问为啥”。

函数指针完成任务的流程是这样的:

获取函数的地址 声明一个函数指针 使用函数指针来调用函数 获取函数地址

获取函数地址那是比较简单的事,如果说 void Hanshu();这是一个函数,那么它的地址就是 Hanshu。 如果函数Hanshubaba();要调用这个函数,是这样的:Hanshubaba(Hanshu); 切记不能写成:Hanshubaba(Hanshu());

声明函数指针

假设现在有这么一个函数:int test3(void *arg); //这个arg参数,回调函数里面用,要解释有点长。 现在要将之改成函数指针形式:int (*test3)(void *arg);

首先,将test3更换成(*test3),因此,(*test3)也是函数,那么test3就是函数指针。 为声明优先级,需要将 *test3 括号起来。

函数指针用武之地

如果你非要我说函数指针存在的意义,那我也真不好给你扯个所以然出来,那我就,举几个用得到的地方吧:

代码语言:javascript
复制
自定义排序/搜索

不同的模式(如策略,观察者)

回调
关于指针的一些思考

前面说到,将指针作为参数传入,在函数内部对指针进行修改,函数结束后指针的修改将被保留。 因为指针传参代表着地址传参。

解惑:如何让对指针参数的修改不被保存。

看个栗子:

代码语言:javascript
复制
class B {
	char* b;
public:
	B() {
		b = new char[5];
		strcpy(b,"aaaa");
	}
	char* get_b() { return b; }
};

class A {
private:
	char* a;
public:
	A(B* temp) { a = temp->get_b(); };

	void set_A() { 
		strcpy(a, "kkkk");	//顶替掉了
	}
};

int main() {
	B* b = new B();
	A* a = new A(b);
	a->set_A();
	cout << b->get_b() << endl;

	return 0;
}

结局打印出来的 b,就是“kkkk”。 那为什么会这样?前面解释过了,a、b都是对内存地址的映射,对a进行修改,就是对地址上的数据进行修改,而b只不过是地址的一个映射而已,读取b,就是读取地址上的东西,那本质已经被改了,读出来的东西自然不一样。

再看个例子:

代码语言:javascript
复制
void Del (POINT_T * the_head, int index)
{
	POINT_T *pFree=NULL;
	
	POINT_T *pNode=the_head;
	int flag=0;
	while (pNode->next!=NULL)
	{
		if(flag==index-1)
		{
			pFree=pNode->next;				//再指向数据域就爆了
			pNode->next=pNode->next->next;
			free(pFree->pData);
			free(pFree);
			break;
		}
		pNode=pNode->next;
		flag++;	
	}	
}

这是链表的一个例子,那可能会纳闷儿,为什么对 pNode执行了 pNode=pNode->next;操作,而the_head却没有跟着变呢? 原因很简单,pNode->next也是一个映射地址,这句话的意思就是用一个新的地址映射,顶替掉那个旧的,使得指针pNode指向一块新的地址,和the_head失去联系。


结构体

结构是 C 编程中一种用户自定义的可用的数据类型,它允许我们存储不同类型的数据项。

代码语言:javascript
复制
struct tag {	// 定义一个结构体,名字叫tag 
    member-list	// 结构体成员变量
    member-list 
    member-list  
    ...
} variable-list ;	// 结构体的简称

如果有简称时,初始化结构体对象是这样的:

代码语言:javascript
复制
variable-list vl;
variable-list *vl2;

如果没有简称时,初始化结构体对象是这样的:

代码语言:javascript
复制
struct tag t;
struct tag *t2;

//就是要带上‘struct’

在一般情况下,tag、variable-list 这 2 部分至少要出现 1 个。


调试

调试呢,是我们解决代码运行过程中突然暴雷的一个很好的手段,如果代码量一大的时候,凭肉眼想找到bug太难了。 但是如果我们对程序的运行流程应该是有一定的设想的吧,就是不知道实际它有没有阳奉阴违。

调试,就是放慢程序运行的速度,让我们看清楚它内部是如何运行的。

1.选择需要检查或暂停运行的行,如下图红色方框前

在这里插入图片描述
在这里插入图片描述

2.点击Windows调试器(或者F5)

在这里插入图片描述
在这里插入图片描述

3、让程序一步步执行,点击单步执行(F10)、进入函数(F11)、跳出函数(shift+F11)、下一个断点(F5) 是可以在代码中打多个断点的。

(每个人的界面排版不一定一样,所以建议使用快捷键法)

程序执行时,可以看到每个变量的状态

在这里插入图片描述
在这里插入图片描述

简单调试就介绍到这里,大家可以先练习一下。

链表

链表在C语言的数据结构中的地位可不低。后面很多的数据结构,特别是树,都是基于链表发展的。 所以学好链表,后面的结构才有看的必要。

初识链表

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。 相比于线性表顺序结构,操作复杂。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而线性表和顺序表相应的时间复杂度分别是O(logn)和O(1)。

但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

链表有很多种不同的类型:单向链表,双向链表以及循环链表。


单链表

在这里插入图片描述
在这里插入图片描述
单链表实现

话不多说啊,这里我只想直接放代码:

代码语言:javascript
复制
#include <stdio.h>	//初学者,C语言开手
#include <conio.h>
#include <stdlib.h>
#include <memory.h>
#include <assert.h>

//节点数据结构体
typedef struct test
{	
	char name[12];		//名字
	char pwd[8];		//密码
	int number;			//编号
	int flag;			//区分管理员和用户	// 	0 超级管理员 1 管理员  2 普通用户 3 屏蔽用户
	int money;			//仅用户有存款,初始500
} TEST_T;

//如果不多来一个数据域,怎么能体现出通用链表的优势
typedef struct reported
{
	int amount;//交易金额
	int rflag; //交易方式	1、存款 2、取款 3、转账转出 4、转账转入
	int lastmoney;//余额
	int lastmoney2;//收款者的余额
	int number1;//付款账户
	int number2;//入款账户
	char time[12];//操作时间	
} REPORT_T;

//节点描述结构体
typedef struct point
{
	void *pData;				//指向数据域
	struct point *next;			//指向下一个节点	
} POINT_T;

POINT_T * head ;
extern POINT_T * head;

这还是个通用链表的头呢!!!

代码语言:javascript
复制
//创建结点
POINT_T * creat(void *data )	//创建一个属于结构体point的函数,
//传入结构体test的指针便可以用以操作test变量,
{								//并返回一个point的指针用以操作point函数
	POINT_T *p=NULL;
	
	p=(POINT_T *)malloc(sizeof(POINT_T));
	if(p==NULL)
	{
		printf("申请内存失败");
		exit(-1);
	}
	memset(p,0,sizeof(POINT_T));
	
	p->pData=data;
	p->next=NULL;	//处理干净身后事
	return p;
}

//新增节点
void add(POINT_T * the_head,void *data )				//这里的data不会和上面那个冲突吗?
{
	POINT_T * pNode=the_head;							//把头留下
	POINT_T *ls=creat(data);
	//后面再接上一个
	while (pNode->next != NULL)							//遍历链表,找到最后一个节点
	{
		pNode=pNode->next;
	}
	pNode->next=ls;			//ls 临时
}

//删除节点
void del(POINT_T * the_head, int index)
{
	POINT_T *pFree=NULL;					//用来删除
	
	POINT_T *pNode=the_head;
	int flag=0;
	while (pNode->next!=NULL)
	{
		if(flag==index-1)
		{
			pFree=pNode->next;				//再指向数据域就爆了
			pNode->next=pNode->next->next;	//这里要无缝衔接
			free(pFree->pData);				//先释放数据
			free(pFree);					//释放指针
			break;
		}
		pNode=pNode->next;
		flag++;	
	}	
}

//计算节点数
int Count(POINT_T * the_head)
{
	int count=0;
	POINT_T *pNode1=the_head;
	while (pNode1->next!=NULL)
	{
		pNode1=pNode1->next;
		count++;		
	}	
	return count;
}

//查找固定节点数据
POINT_T * find(POINT_T *the_head,int index)
{
	int f=0;
	POINT_T *pNode=NULL;
	int count=0;
	pNode=the_head;
	
	count=Count(the_head);
	
	if(count<index)	
		printf("find nothing");
	
	while(pNode->next!=NULL)
	{
		if(index==f)
			return pNode;
		pNode=pNode->next;
		f++;		
	}
}

我就挑简单的讲,先入个门,之前有专门的数据结构专栏,后面也会有专门的数据结构专栏,一步一个脚印,现在都不要太着急。

在这里插入图片描述
在这里插入图片描述
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/04/27 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 指针和动态内存分配
    • 引用&
      • 将引用用于结构
      • 何时使用引用参数?
    • 指针
      • 指针和const
      • 通过指针返回字符串的函数
      • 通过指针返回结构
      • 函数指针
  • 结构体
  • 调试
  • 链表
    • 初识链表
      • 单链表
        • 单链表实现
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档