翁恺_C语言_2

第七章:函数

  1. 为什么要有函数:以求和函数为例

  1. /*代码复制是程序质量不良的表现*/

什么是函数?

  1. 函数是一块代码,接受零个或多个参数,做一件事情,然后返回零个或一个值。

  1. void:一种数据类型,表示该函数被调用后不返回任何的数据类型。

  1. 函数原型:

  1. 作用:不仅仅检查你对函数的调用是不是对的,也用于检查你对函数的定义是不是对的

  • 函数原型里面一定要把参数类型写全,如果确定函数里面是没有参数的,就需要写清楚 void function(void),表示这个函数没有参数传递。但是函数原型内是可以不传入参数的。比如下面的这个函数原型可以写成:double max(double, double);

  1. 参数传递

  1. 之前有形参和实参的概念,现在不这么叫了,叫参数和值

  1. 变量的生存期和作用域&本地变量

  1. 对于本地变量而言,这两者统一在大括号(块)内。

  1. 本地变量的规则

  1. 一些关于函数的细节问题

  1. C语言不允许函数的嵌套定义,在一个函数里可以放另一个函数的声明,但是不能放另一个函数的body(定义)

  1. 关于main

  1. int main()也是一个函数

  1. 可否写成 int main(void)? 可以

  1. int main(){ return 0; } 中的return 0; 是否有意义? 有,可以被返回,因为运行代码是不止main()函数的主体被运行,其前前后后还有一些辅助的代码被运行。正常被运行的代码最后return的都是0,所以最后加上return 0; 并不会报错,但是若return其他非0的值,就会报错。

第八章:数组

1.数组的定义和使用

  1. 定义:从C99开始,可以使用变量来定义数组的大小

  1. 数组的特点(可以看作一个容器): /*数组的下标从0开始,要防止数组越界*/ /*长度为0的数组可以创建,但是无用

*/

  1. 数组的集成初始化的几种情况: // 第二种情况,如果只赋一个值,则后面的元素自动赋值为0;

  1. sizeof函数计算数组的大小

  1. 数组的赋值:不能直接将一个数组变量赋值给另一个变量,必须使用遍历才能把一个数组的所有元素交给另一个数组

  1. 数组作为函数参数时,往往必须再用另一个参数来传入数组的大小

// 作为参数时,数组的形式和指针的形式是一样的;如果不希望函数修改传进去的数组,则应该将数组写成 const

2.二维数组

  1. 创建

  1. 遍历: a[i][j]表示第i行第j列上的单元;a[i,j]中的逗号表示运算符,等于a[j]

  1. 初始化:

第九章:指针

1.取地址运算符 &: 整数地址的大小并不永远与int相同,这与你的架构是32位还是64位

  1. & 只能取变量的地址; 相邻的数组单元的地址差距永远是4;

2.指针

指针类型的变量就是保存地址的变量

// 对变量赋值0x是以十六进制数作为变量的值进行运算的。一般用来表示内存地址的。

// 用 { printf("a=%p\n", a) } 查看a的地址

当变量p里面的值是变量i的地址,这时候就称:p指向了i

定义:是把 * 加给了p,而不是加给了int,第三行和第四行的意义相同,定义p是一个指针,且 * p 和 q 都是一个 int 类型 // 好习惯:任何指针一旦定义就初始它为0

作为参数的指针:

访问某个地址上的变量:* ; // 在int * p 中星号没有充当运算符,只是强调p是个指针;其他情况如*p前没有int,表示读取指针p这个变量值为地址的值

指针的出现使得函数可以反作用于主函数体:// 以往调用函数只是传值进去作为一个局部变量,在函数内部改变这个变量对原主体中的变量没有影响;但是若传地址进函数,则改变该地址指向的值,则原主体中的变量的值也会改变。 // 地址对应的变量的值是唯一的

左值与右值:左值之所以叫左值,出现在赋值号左边的不是变量,而是值,是表达式计算出的结果(是一次运算),特殊的值,所以叫左值。 // 下面的 *p表示“取指针p指向的值”表达式计算的值。

3.指针的应用

应用场景一:交换两个变量的值

应用场景二:函数返回多个值,某些值就只能通过指针返回,传入的参数实际上是需要保存带回的结果的变量

应用场景三:函数返回运算的状态,结果通过指针返回

指针最常见的错误:

  1. 定义了指针变量,还没指向任何变量,就开始使用指针

  1. 任何地址变量,没有得到任何实际变量地址之前,不能用*p来改变p指向的值,因为此时的p可能是一个乱七八糟的地址,很可能指向系统中某些不能改变的值

4.指针和数组

// 在32位架构系统中,地址是32位,也就是4个字节,sizeof()求出来的大小也就是4

以下四种函数原型是等价的

数组变量是特殊的指针,取数组的地址,不用加&符号,直接拿数组变量的名字,就可以得到数组的地址。数组的单元表达的是变量,需要用&取地址。a本意表示的就是数组的第一个元素地址,因此数组第一个单元的地址与数组的地址相等。

在之前定义数组的时候:int b[] 可以被看作 int * const b ,表示b是一个常量指针,一旦b被初始化出来之后,就不能再改变。

5.指针与const

const是一个修饰符,它加在变量之前,表示这个变量不能被修改

当指针是const时:

当指针指向的变量是const时: // 指向的那个变量仍可以改变,只是不能通过指针来对这个变量进行修改

下面的几种表示就两种意思:① const在*后,表示 指针不可修改;② const在*前,表示 通过指针不可修改;

转换(一种常用的传结构的方法):

const 数组:可以保护数组值

6.指针运算

// 0x2c = 44 ; 0x30 = 48 ; sizeof(char) = 1 ; sizeof(int) = 4 ;

  1. 对指针做一个加1的动作:意味着把它移到下一个单元去。并不是直接在地址上加1,而是在地址上加一个 sizeof(指针所指的类型)。在数组中,相邻单元地址的差距就是基础类型的大小。

  1. 指针计算: 指针相减表示这两个指针中包含多少个该类型的单元

  1. 指针比较 和 0地址:

// 目前的操作系统都是多进程的,对于进程而言,操作系统会给每个进程一个虚拟的地址空间。所有程序在运行时都有一个从0开始的连续的地址空间

  1. 指针的类型:

  1. 指针的用途

7.动态内存分配

malloc返回的类型是 void * ( 一个没有类型的指针),用于分配一个占多少字节内存的变量。用完之后,要使用free()还掉这个内存。

几种情况:0和NULL可以被free(),1不能被free();对p申请malloc之后,但是又对指针p进行了修改,对修改后的指针进行free就会报错;free过后的内存就不在名单里了,不能再被free。总之:除了0和NULL,被free的指针,一定得是申请过内存的指针或一个实值变量。

申请了内存(malloc)后要对其进行free,这是一个良好的习惯。

一些常见的问题:

第10章:字符串

1.字符串定义

最后一个单元为0,且每个单元都是一个字符的字符数组,是C语言中的字符串

// '\0' 和 '0' 是不一样的,'\0'代表整数0,'0' 代表一个字符——人可以读到的0,其大小为48

字符串字面量:"Hello",在你写出来一个字符串的字面量表达形式之后,编译器会自动帮你在结尾生成一个“\0”

2.字符串变量

用指针和数组来定义字符串时,效果是不一样的:用指针来定义字符串时,不能通过指针对字符串进行修改

两种形式使用的时机?

使用char*定义的并不一定是字符串,可能指向的是字符数组

3.字符串输入输出

字符串赋值: // scanf("%7s", string) 表示函数最多读入7个字符

常见错误:char *string 只是定义一个指针变量,并没有定义一个字符串变量

空字符串:

4.字符串数组

char a[][10] 和char *a[] 的区别:前者是定义一个二维数组,每一个a[i]内最多可以存放长度为9的字符串,后者是定义了一个指针数组,每一个a[i]分别指向一个字符串。

main()函数其实是有参数的:可以读取到在命令行中执行这个程序时后面跟的参数。其中第一个参数就是执行的这个可执行程序的名字

单字符输入输出:

EOF(-1): 表示写失败。(end-of-file) 是在C语言中定义的宏,这个宏的值是-1

getchar()每次都是读一个字符,一个char

5.字符串函数

strlen函数 // 以下这些函数的原型在头文件<string.h>中 #include <string.h>

// size_t 也是一种返回类型,其实就是整型

// 作为参数时,数组的形式和指针的形式是一样的;如果不希望函数修改传进去的数组,则应该将数组写成 const

strcmp函数 // 用 s1 == s2 来比较两个数组变量时,其实是比较两个变量的地址,这是永远不会相等的

// 空格在ASCII码中是32

strcpy函数 // restrict 是一个关键字,表示拷贝到dst后,不能与src在内存中重叠

strcat函数

字符串搜索函数

strchr表示从一个字符串中找从左边第一个c出现的位置,返回的是指针,指向的是要找的那个字符;strrchr表示从右边开始找。

strstr函数用来在字符串中找字符串;strcasestr功能一样,但是在寻找时会忽略大小写

第11章 结构体

1.枚举(enumeration)

color可以在前面加上enum充当一种数据类型:enum color。一般不会用,用的是大括号内的red、yellow、green。若要使用color来定义数据类型的时候,必须在前面加上enum。

在枚举中所有有意义的名字后加一个NumCOLORS,它的值等于该枚举中所有有意义的名字的数量。

枚举实际上就是int,它的意义大多是用于定义一排意义上排比的名字,这比const int方便。

2. 结构类型

// 结构类型 和 结构变量 是两件事情

结构类型的声明:声明结构类型时不要忘了分号。并且和本地变量一样,若希望多个函数可以使用这个结构体,则需要在函数外部声明。

结构类型的使用:第一种:struct point p1, p2; 代表定义了两个变量,这两个变量的数据类型都是 struct point;第二种,定义了两个没有结构名的变量。第三种,最常用。

结构的初始化:在定义是若没有设定值,则自动设置为0。这点和数组的初始化有点相似。

结构成员:结构类型是虚的,不是一个实体,结构变量才是实体。出现在 . 的左边的一定是一个结构变量而不是一个结构类型;

(struct point){5,10} 表示将5,10这两个int类型的值,强制类型转换到struct point这种结构类型同时赋值给p1;

数组类型不能做做下面的那种赋值,因为数组变量是 const 类型。

结构指针:和数组不同,结构变量的名字并不是结构变量的地址。用结构体定义出的结构指针,是结构变量的地址。

3.结构与函数

结构作为函数参数时是与数组不一样的,数组作为参数传入函数中,传入的是其指针,对其修改可以修改外部的值,而结构不能。

&today.month 说明取成员 . 的优先级比取地址 & 的优先级高

输入结构:不像int , float那样,没有直接的方式可以一次scanf一个结构。

当一个结构变量today作为参数传递时,传到函数内的不是这个结构变量,而是这个结构变量的值。传到函数内的是一个与today的值相等的另一个该结构的变量。

指向结构的指针: -> : arrow,“->”也是一个运算符,和“.”运算符的地位差不多

K & R : 如果你有一个比较大的结构变量要传入一个函数,通常更为有效的方式是传指针,而不是拷贝整个结构(既费空间,又费时间)。

重点:(*p).month 与 p->month 的效果一样,都是从结构变量*p中取一个成员。

声明任何一个结构体之后,可以使用 ''struct 结构体名 *p'' 来定义一个指针p,表示这个指针指向一个这种结构体的结构变量。也就是说声明一个结构体后,既可以用它来定义结构变量,也可以用它来定义结构变量的指针。

4.结构中的结构

在声明了一个结构类型之后,我们可以做出这个结构类型的变量,也可以做出这个结构类型的数组。

结构中的变量可以是int/float/double类型,也可以是另一个结构,

5.类型定义

typedef关键字:比如下面的语句中,就可以用 Data 来代替 struct AData 来定义结构类型变量。

6.联合(没看太懂)

union: 结构上看起来与struct差不多,但是union定义的类型中的每一个成员都使用着同一个内存空间,但是struct定义的类型中的每个成员占用着各自不同的内存空间。

union的使用场合

第12章

1.全局变量

定义在函数外部的变量是全局变量,具有全局的生存期和作用域,它们与任何函数都无关,在任何函数内部都可以使用它们。

// 在函数内部使用 __func__ 表示当前这个函数的名字

全局变量有个好处,若没有进行初始化,则会得到一个0值。但本地变量若没有进行初始化的话,则会得到乱七八糟的结果

下面的结果会报错,但是如果在 int gAll = 12; 前加一个 const,就没问题。

如果函数内部存在于全局变量同名的变量,则全局变量被隐藏

2.静态本地变量

在本地变量定义前加:static修饰符

返回一个本地变量的地址(钥匙),让外面的程序继续使用它是有风险的,因为出了这个函数这块空间就会被系统收回并分配给下一个函数中的本地变量。但返回全局变量或静态本地变量的地址是安全的,因为系统不会收回其指向的空间。

*p的地方在f()中叫做i,当退出f()函数后,这个空间虽然值还是i,但是已经不叫i了,当进入到g函数时重新向系统申请空间,就得到了得到了这块值为12的空间,并把这个地方叫做k。

3.宏定义

以 # 开头的语句不是C语言

# 编译预处理指令:include就不是c语义的关键字,但是开头必须用到 #include 编译预处理指令。

#define PI 3.14159 : #define 定义了一个符号PI,这样的符号叫做一个宏PI,它的值为3.14159。

C语言在编译之前,会先进行一次编译预处理,在编译预处理时会将程序中所有 PI 替换成3.14159。但是编译器不会管“”字符串内的内容。 // 其实就是纯文本替换

定义一个没有值的宏:作用是告诉编译器代码中有没有这个宏,如果存在,编译这部分代码,如果不存在,则编译另一部分代码

预定义的宏:一般用来表示一些特殊的东西,可以让编译器替你插入一些特殊的值。__LINE__:代码当前所在的行号 ; __FILE__ :源代码文件的文件名 ; __DATA__ :编译时刻的日期; __TIME__:编译时刻的时间;__STDC__:如果当前编译器符合ISO标准,那么该宏的值为1,否则未定义

4.带参数的宏

下面的这个cube(x) 在定义时,x并没有类型,替换时也不会进行任何的类型检查

宏定义时一定记得要将替换的内容所有都包在圆括号内 ,并且所有的x也应该在圆括号内

定义宏的时候千万不要加分号,否则分号也会被替换到代码中

5.多个源代码文件

编译器对项目中的.c文件的编译过程是:先将每个.c文件进行compile生成.o文件,然后链接器将这些.o文件build到一起生成.out文件

6.头文件

如果在主文件中没有另一个文件中函数的函数原型,则C编译器会猜测它是int类型,即使这个函数的参数和返回都是double类型。最后编译会通过,运行可能也会通过,但是结果不对

如何使主文件中对其他文件中的函数使用是一致的呢? 使用 头文件 作为中间的媒介

特别是做大程序的时候,这种方法非常的好用。

#include 也是一个编译预处理指令,和宏一样,在编译之前就被处理了。它做的事情其实就一个,将.h文件的内衣原封不动的插入到它所在的那一行

对于unix系统,标准头文件在 /usr/include/stdio.h

#include 的几个误区

现在的c语言编译器会默认将所有的标准库的代码与你的代码链接在一起,形成一个可执行文件,当然用不到的会自动去掉。因此即使你没有 #include <stdio.h> 或 #include <stdlib.h> printf 和malloc函数依然能用,这些头文件只包含了这些函数的函数原型。

不对外公开的函数和全局变量的方法,就是在他们前面加上static

7.声明

max.h中的函数原型就是对这个函数的声明,那如何在 max.h 文件中给出max.c文件中的某个全局变量的声明?

声明和定义;只有声明可以被放在头文件中

在一个.c文件中,不能让一个结构的声明出现两次

报错的情况:出现两次同名的结构体声明

使用标准头文件结构,不会重复进行定义,原理是使用宏的条件编译

标准头文件结构:保证这个头文件在一个编译单元(.c文件)中只会被 include 一次

Visual studio 有另外一种写法,叫 #pragma once 也能起到相同的作用

第13章

1.格式化输入输出

printf()函数

printf("%9d\n", 123) --> " 123" // 9代表输出的结果要占据9个字符的空间,靠右对齐,d表示整形

printf("%-9d\n", 123) --> "123 " // - 表示左对齐

printf("%+9d\n", 123) --> " +123"

printf("%+-9d\n", 123) --> "+123 "

printf("%-+9d\n", 123) --> "+123 "

printf("%-+9d\n", -123) --> "-123 "

printf("%-9d\n", -123) --> "-123 " // 即使没有+号,因为输出就是负数,最后结果还是有-号

printf("%09d\n", 123) --> "000000123 "

printf("%9.2f\n", 123.0) --> " 123.00" // 整个输出占据9个字符的位置,.2表示小数点后有两位,f表示浮点数

printf("%*d\n", 6, 123) --> " 123" // *对应6(6也可以是一个变量,就会变得更灵活),输出占据6个字符的位置,并且下一个d才是字符数(123)

[hIL] : 修饰符;type:输出类型;

printf("%dty%n\n", 12345, &num); --> " 12345ty" // %n表示printf做到这个地方的时候,已经输出了多少个字符,然后将这个值赋值给&num指针所指的变量

printf("%d\n", num); --> "7"

scanf()函数:

scanf("%*d%d", &num); //表示输入两个数,跳过第一个数,并把第二个数赋值给num变量

用scanf("%i", &num); 会根据你输入整数的情况,决定到底以哪种形式(16进制、8进制)读进来

%*[^,]表示从上面的语句最开始读,直到第一个逗号之前的所有内容跳过不要,","读取内容中的逗号,%[^,]表示读取接下来逗号之前的内容作为一个字符串交给sTime,

这两个函数是有返回值的,scanf的返回值是这一次读取了几个item(变量)进来,而printf的返回值是这一次输出了多少个字符,

2.文件输入输出

程序运行时的重定向:用"<"指定一个文件作为输入,用">"指定一个文件将输出结果输出进去;

./test > 12.out : 将test运行时输出的内容重定向到(输入到)12.out文件内

cat > 12.in :把将要输入终端的内容连接到(输入至)文件12.in文件的末尾

More 12.out: 把文件12.out的内容输出到终端

./test < 12.in > 12.out : 将12.in 的内容作为 test 的输入,并将输出的内容输入至 12.out 文件中

但一般的方法是定义一个 FILE 结构的变量fp,在stdio.h头文件中已经声明好了 FILE 的结构类型,用 fopen()来打开文件,然后使用fscanf()、fprintf()做文件的读和写

如果fopen()函数没有打开一个文件的话,则会返回一个 NULL ,用if判断一下

r+:一般是用来修改,可以读,也可以写

w+:一开始是没东西可读的,但是可以读出你写进去的这些东西

在w、a之后还可以加一个x,表示只新建,如果文件已存在则不能打开,避免对已有的文件做破坏

3.二进制文件

在一个平台上写的二进制文件,在另一个平台上不一定能运行出来。架构可能不一样,比如int、long等在x86 和 x32 上的字节数不一样

对于一个程序而言,要在一个文件中,是因为需要一些配置性的东西。在unix中这些配置在一个文本文件中,在windows中这些配置文件在注册表中

注册表是一个非常大的二进制文件,整个Windows所有软件的配置信息全部写在这样一个大的二进制文件内

使用fread()、fwrite()来完成二进制文件的读写,参数表:(const) void *restrict ptr是要读或写的那块内存的指针,size_t size是这块内存有多大,size_t nitems是有几块这样的内存,最后一个参数是文件指针

fseek是将光标位置放到文件中某个位置,SEEK_END表示从尾开始,0L表示从尾部数第0个位置,所以位置就放在的末尾。

ftell()是得到现在文件中所在的位置,移到最尾巴上得到的位置就是该文件的大小

为了解决不同架构平台上整数类型、大小端的不同,可以放弃使用int,而是使用typedef直接定义一个明确大小的类型,但更好的方案是文本来解决跨平台的问题

4.按位运算

C语言被称为接近底层的语言,任何一个新机器、硬件出来之后,C语言是除了汇编之外唯一可以直接在上面运行的语言。C语言提供了一些比较接近底层的操作,如果你不是直接去操纵硬件或做底层的事情,一般是用不到的。

按位与&时,只有两个数都是1时,结果才为1;

按位或 | 时,有一个1就是1,全为0时才为0;

取反是把1变成0,把0变成1求得反码;求补码和取反是不一样的;c的补码是-c,补码等于反码加一;

实际上在计算机内部只有按位运算

把一个int数往左移n位,等价于乘以2的n次方;把一个int数往右移n位,等价于除以2的n次方。

无符号(unsigned)和有符号(signed)两种类型(float和double总是带符号的),在除char以外的数据类型中,默认情况下声明的整型变量都是有符号的类型;char在默认情况下总是无符号的。在除char以外的数据类型中,如果需声明无符号类型的话就需要在类型前加上unsigned。

C语言中的 unsigned int 和 signed int 类型的区别,最大的区别就是最高位是否用来做符号位。由于在计算机中,整数是以补码形式存放的。根据最高位的不同,如果是1,有符号数的话就是负数;如果是无符号数,则都解释为正数。

另外,unsigned若省略后一个关键字,大多数编译器都会认为是unsigned int。

往左移是不管符号位的,往右移是要管符号位的。并且移位的位数不要用负数

1u表示无符号整型的1

位段:这样一个结构包含若干个成员,冒号后代表这个成员占据几个比特,所有的成员最后作为一个int

总的来讲,位段一般用于底层的操作硬件

第14章 可变数组和链表

1.可变数组的create和free

用C语言的代码实现一个可以变大小的数组,并且能够获取当前数组的大小,能够访问每个单元。其实就是设计一个结构体,并对这个结构体设计一系列的函数

Void array_inflate():让这个数组长大

在定义一个结构类型时,尽量不要把带上星号,也就是尽量不要定义一个指针类结构类型。

否则在定义一个本地变量的时候,Array a,这个a就不是本地的了,而是一个指向其他某个地方的指针。并且对于读者来说不容易想到,a是个指针

创造动态数组:array_create() // 为什么这个函数返回的是a结构体,而不是a的指针呢,因为如果返回的是a的指针,则在main()函数中的本地变量 a就不能算是一个本地变量了,变成了一个全局变量,返回一个结构体赋值给一个新建立的本地变量a,这样的操作刚刚好,离开main后空间被回收。

array_free():因为Araay结构体局部变量a中的第一个成员是一个指针,但是在离开main之前需要将a的空间进行回收,除了指针所指的外部空间外,其他空间会被自动回收。所以需要在离开main函数之前使用array_free()函数对a的第一个指针成员进行free。因此传入的参数需要是a的指针才能对其进行操作,操作的对象是a指向的第一个成员。

2.可变数组的数据访问

使用封装的形式来查看动态数组的size:可以起到保护a的size。这是因为现在获取size这个功能确实简单,一行代码a.size就能够搞定,但是随着未来程序的复杂,对于一个功能,并不是一行代码就能够给你结果的。这样相当于给了你一个函数,这个函数把我的内部的实现细节给保护起来了,你看不见,这就是好处。

// 需要对动态数组操作的函数,传入的参数一般不const一下;只需要进行访问的函数,一般const一下进行保险操作。

array_at()函数查看动态数组某个位置上的元素:返回动态数组某个位置上元素的指针,这样的好处是可以使用这个函数对动态数组的某一个元素进行赋值;当然也可以不返回指针,设置array_get()、array_set()函数如下。

3.可变数组的自动增长

array_inflate()函数:使数组进行长大,但是malloc()函数出来的东西其实不能长大,所以需要malloc()新的空间。为了防止array_at()函数在给动态数组赋值时越界,array_inflate()函数一般用在array_at()函数中。为了一次能增加一定的空间,在最前面定义一个常量 const BLOCK_SIZE = 20,每次增长20个空间内存。

4.可变数组的缺陷

每一次使用inflate长大的时候,都要申请一块新的内存空间, 然后进行拷贝。首先,拷贝要花时间,特别是当数组特别大的时候(一万,一百万)。其次,可能即使你有本次增长足够的内存空间,但你也无法申请内存空间,原因就是没有连续的大内存空间了。虽然之前的内存空间被free掉了,但是跟后面剩下的内存空间并不连续,这样并不能使用。

如果在增长的时候,原来数组的内存不动它,我们并不是申请一个新的大内存,而是只申请一个block那么大的内存,然后把他们链起来,这样也不需要拷贝了,又能够充分利用内存空间中的每一个角落

5.链表(Linked List)

实际的实现并不像上图那样的效果一样,而是每一个内存中有两个东西,一个是数据,另一个是指针,这个指针指向下一个同一个东西。 另外有一个指针(head)需要指向第一个。这样一个东西叫做结点,整个的结构叫做链表

在程序中,将结点定义为一种结构结构中包含两个东西,一个存数据,一个是指向下一个节点结构的指针。对于节点结构体head,首先将其定义为NULL。然后每添加一个新的node,需要让刚才末尾结点的指针成员指向新的node,并让新的node的指针成员指向NULL。

head只是一个指针,当链表一个元素都没有只有一个头指针 head == NULL 时,往里插入一个元素,last也是NULL,if语句不执行,else内的执行。让头指针head指向新插入的元素 *p 的指针 p。此时链表有了一个元素,并且头指针head为这个元素的地址p。再往里插入一个元素时,last此时是头部指针指向的结点的地址,也就是第一个结点的地址,然后进入if语句内,找到最后一个结点,使最后一个结点的next指针等于新加入结点*p的地址p。

// 重点:(*p).month 与 p->month 的效果一样,都是从结构变量*p中取一个成员。

// Node *last = head; 的意义是令last==head,head是一个指针,last也是一个指针,只不过二者指向的内存空间是一个Node结构体?

6.链表的函数

为“输入一个number作为链表的最后一个节点挂在它的尾巴上”这件事设计一个函数

二级指针,指针的指针,head是指针, Node** pHead 是指针的指针。(pHead代表head的指针)

head是一个指针变量,这个变量的值是地址

但是这样每往里面加入一个值的时候,就要从头开始找到尾端,这样有点麻烦,能不能有一个指针始终指向尾部结点?设计一个tail指针(尾指针)

head是一个指针(地址),在add函数内部,需要对head的值修改成第一个结点的地址。所以如果将head的值直接传入给add函数,在add函数内部修改的则是另一个值等于它的局部变量。所以需要return这个修改后的值再赋值给head,或者直接将head的指针传入给add函数。

另一种方法是:用一个自己定义的结构类型List,里面包含一个Node* head指针,这样做的好处是可以用这种List数据结构来代表整个链表。现在在这个数据结构中我们只放了一个head,但是后面可以再加其他结构,比如说加一个tail尾指针。

// 第25行代码应该把 head = 去掉

7.链表的搜索、删除和清除

搜索

设计一个函数将链表内的每一个元素都输出出来

设计一个函数,输入一个数字后,在链表中找到这个数字

删除

任务:输入一个数后,在链表中找到这个数的结点并删除它

需要有的功能:找到这个结点后,首先将前面一个节点包含的指向指针指向后面一个结点,之后再free当前的这个结点。 // 要考虑边界条件,第一个就是要找的数字的情况

清除

整个链表都是malloc出来的,所以最后需要有首有尾,将它清除

8. 域

在链表结构中,每一个节点中,数据域是结点中存储数据元素的部分,指针域是结点中存储数据元素之间的链接信息即下一个结点地址的部分。

猜你喜欢

转载自blog.csdn.net/DUDUDUTU/article/details/129283592