手把手指导编写数据结构(1)——C语言基础

前言

由于本人本科不是计算机相关专业,本科唯一学习的一门编程语言便是C语言,还是大一下学习的,距今已经过去四年多了,中间基本没有怎么写过代码。因此现在阅读数据结构相关书籍时,很多代码实现看着有些生疏,所以在正式开始学习之前回顾一下数据结构中经常会用到的C语言基础
如果您也和我一样转行而来,半道出家,对本文内容有疑惑或者异议,欢迎一起交流。本人才疏学浅,如果内容有误,还请提出指正。

条件编译

如果一个头文件被引用两次,编译器会处理两次头文件的内容,这将产生错误。为了防止这种情况,标准的做法是把文件的整个内容放在条件编译语句中,如下:

#ifndef LINKLIST_H
#define LINKLIST_H

typedef int ElementType;
typedef struct Node *LinkList;

#endif

这种结构就是通常所说的包装器 #ifndef。当再次引用头文件时,条件为假,因为 LINKLIST_H 已定义。此时,预处理器会跳过文件的整个内容,编译器会忽略它。

指针

C语言的精髓便是它的指针了,指针操作可以相当便捷地对各种已创建的数据结构进行操作;同时C语言的指针也是初学者比较难理解的部分,这里简单地对指针的知识点进行归纳总结。

指针的声明及本质

首先,指针本身代表着一块内存的地址。根据这块地址,我们可以对地址指向的内存中的内容进行操作。
这里我们对常见的指针相关声明进行归纳:

  • 变量类型(int/char/float.etc)+“ * ”+变量:声明指向一个变量的指针,即声明一个指针变量
  • “ * ”+指针变量:存储在指向地址内存中的内容
  • “ & ”+变量:该变量所在的地址
    下面结合实例进行解释说明:
int	*ip;    	//声明一个整型的指针
int	var = 10;	//声明一个整形变量

ip = &var;		//在指针变量中存储var的地址
int	var1 = *ip;	//访问指针指向地址中存储的内容

如果将上面的变量在调试中进行查看,可以看到,ip本身是一个地址值,而ip指向的内容是10;变量var1的值也是10

数组与指针

在声明数组的同时,该数组的变量名同时也代表着数组首元素的地址。因此,将数组地址赋给指针变量可以通过下面两种方式:

int	a[10];    		//声明一个数组
int	*ip = a;		//指针变量存储数组首地址
int *ip1 = &a[0];	//指针变量存储数组首地址

上面两种方式完全等价。另外,由于把首元素的地址存储在 ip 中,我们也可以使用 * ip、* (ip+1)、*(ip+2) 等来访问数组元素。

分配内存

malloc()

我们在创建一个的数据结构或者新的结点时,往往会为其分配一块足够大的内存,此时我们就需要使用到malloc库函数了。
首先,要使用该库函数,头文件必须包含
malloc(size_t size)函数描述:

  • 所需参数:内存块的大小,以字节为单位。
  • 函数功能:请求分配所需的内存空间。
  • 返回值:一个指向该内存的指针(地址)。如果请求失败,则返回 NULL。

返回参数是一个地址,因此可以将该函数的输出赋值给一个指针变量,但是需要注意最好指派适当的类型(C++要求必须指派)。同时,该指针变量如果指向了一个数组的首地址,则其可以作为数组的名称使用。

sizeof关键字

一般malloc的输入参数不会直接填字节数,为了方便我们会使用sizeof关键字作为参数传入,实现根据不同类型或者结构体动态分配的功能。
sizeof 运算符可用于获取结构、共用体和其他用户自定义数据类型的大小。使用 sizeof 的语法为:

sizeof(data type)

其中,data type 是要计算大小的数据类型,包括结构、共用体和其他用户自定义数据类型。

free()

释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。常用在删除数据结构、相关结点的时候。
free(*ptr)函数描述:

  • 所需参数:需要释放内存的指针(地址)。
  • 函数功能:释放指定地址的内存。
  • 返回值:无返回值。

free是对应calloc、malloc 或 realloc 的,因此在一个完整的程序中它们应该是成对存在的,也就是说:在free内存空间之前,一定是要在该内存已经由calloc、malloc 或 realloc分配过了才行
另外还需要注意,虽然我们将内存空间释放了,但是指针依然存在,指向原来的位置,只是里面无法读取而已,这时这个指针称为野指针,我们应该在free之后再将指针置空

下面为分配内存及释放内存的示例。我们创建一个能放置15个字符的字符串指针变量,使用sizeof(char)程序自行计算一个字符需要的大小,接着再乘上15表示可以放置15个字符。然后使用free将其内容删除,最后为了避免野指针问题,将指针置空。

char *str = (char *)malloc(sizeof(char)*15);//分配15个字符空间
free(str);				//释放内存
str = NULL;				//指针置空

结构体、结构体指针

在一个数据结构单元中,往往包含了各种不同数据类型的信息,这时我们应该把它们看作一块整体,为此,需要定义一个结构体。

结构体的声明定义

结构体声明定义使用struct关键字,使用起来非常简单,这里直接举例说明,假设我们创建一个结点,结点包含信息有一个数值以及一个指针地址。

struct Node {
     
	int Element;
	int *Next;
};

声明成功后,我们便可以依次创建一个结构变量了:

struct Node List;

看到上面这行,编译器会创建一个名为List,使用Node结构设计的结构变量。此时的struct Node起着和int、char类似的声明变量作用。实际上这一行是下面完整版的简化:

struct Node {
     
	int Element;
	int *Next;
}List;

同时我们也可以看作将最初的两种操作合并了起来,直接写成上面这样更加简洁。

结构指针

与int、char类似,结构体一旦声明成功,也可以用来声明指针变量,此时该指针变量称为结构指针,可以代表结构体的地址。
结构指针相比于直接使用结构体有如下优势:

  • 更加方便操作;
  • 由于只需传递一个地址信息,所以执行更快;
  • 在早期的C实现中结构不能作为参数传递给函数,但是指向结构的指针可以。

声明结构指针示例如下:

struct Node {
     
	int Element;
	int *Next;
}*L;
/*上下为两种等价的声明方式*/
struct Node *L

访问结构体成员

对于结构体,直接可以使用运算符“ . ”来访问成员。
对于结构指针:有两种操作方式,一种是结构指针+“->”+成员名;另一种是(“ * ”+指针变量)+“ . ”+成员名。
下面是示例:

//结构体访问成员
List.Element;
//结构指针访问成员
L->Element;
(*L).Element;

typedef关键字

在最后,介绍一个常用的关键字。在写数据结构时,我们经常为了描述方便,提高代码可读性,会为很多结构和数据类型定义一个更加好理解的名称。
typedef可以为某一类型创建自己想要的名字。和#define不一样,其给出的符号名称仅限于对类型,不能对值
这里给出一个实例进行讲解说明:

typedef int QElemType;
typedef struct QNode 
{
     
	QElemType data;
	struct QNode *next;
}QNode,*Queueptr;
typedef sturct
{
     
	Queueptr front;
	Queueptr rear;
}linkQueue;

这是链队列的结构体声明代码。首先第一行对int类型取了一个QElemType的名字,表示队列中元素值都是int型的数值
接着来看第一个结构体:

  • struct QNode 是一个结构类型,就和int,char之类差不多,是类型说明符
  • struct QNode *next 定义了一个struct QNode类型的指针,就像 char *p一样;
  • typedef struct QNode QNode:struct QNode这个名字太长了,下面用起来不方便,所以要再给它一个名字,所以使用struct QNode 和QNode是一个意思了;
  • 再看*Queueptr,这也是一种重命名,是为了方便下面引用struct QNode类型的指针;

再看第二个结构体定义,这里面定义了两个指针,分别指向队头和队尾。Queueptr front也可以写成struct QNode *front。

最后我们来思考一下,第二个结构体的struct关键字后是没有另外的名称的,然而第一个结构体的关键字后面多了一个QNode,那这里的QNode可以不要吗
答案是不可以不要的,因为在这个结构里,用到了自身的类型:struct QNode *next; 如果没有事先声明,那么这里就会报错。

参考文献

《C Primer Plus(第五版)》

你可能感兴趣的:(数据结构与基础算法,数据结构,c语言,指针)