C语言学习记录
本文最后更新于:2023年11月10日 上午
C 数据类型
C 中的类型可分为以下几种:
整数类型
注意,各种类型的存储大小与系统位数有关,但目前通用的以64位系统为主。
浮点类型
void类型
void 类型指定没有可用的值。它通常用于以下三种情况下:
C 变量
基本类型:
1.注意,赋值表达式有返回值,等于等号右边的值
1 |
|
2.头文件 stdbool.h 定义了另一个类型别名 bool ,并且定义了 true 代表 1 、 false 代表 0 。只要加载 这个头文件,就可以使用这几个关键字。
1 |
|
C 数组
数组的地址
数组是一连串连续储存的同类型值,只要获得起始地址(首个成员的内存地址),就能推算出其他成员 的地址。请看下面的例子。
1 |
|
由于数组的起始地址是常用操作, &array[0]
的写法有点麻烦,C 语言提供了便利写法,数组名等同于
起始地址,也就是说,数组名就是指向第一个成员( array[0]
)的指针。
1 |
|
上面示例中, &a[0]
和数组名 a
是等价的。
这样的话,如果把数组名传入一个函数,就等同于传入一个指针变量。在函数内部,就可以通过这个指 针变量获得整个数组。
函数接受数组作为参数,函数原型可以写成下面这样。
1 |
|
上面示例中,传入一个整数数组,与传入一个整数指针是同一回事,数组符号
[]
与指针符号 *
是可以互
换的。下一个例子是通过数组指针对成员求和
1 |
|
*
和 &
运算符也可以用于多维数组。
1 |
|
上面示例中,由于 a[0]
本身是一个指针,指向第二维数组的第一个成员 a[0][0]
。所以,
*(a[0])
取 出的是 a[0][0]
的值。至于
**a
,就是对 a
进行两次 *
运算,第一次取出的是 a[0]
,第二次取出的是
a[0][0]
。同理,二维数组的 &a[0][0]
等同于
*a
。
不能将一个数组名赋值给另外一个数组名。
数组指针的加减法
1 |
|
上面示例中,通过指针的移动遍历数组, a + i
的每轮循环每次都会指向下一个成员的地址, *(a + i)
取出该地址的值,等同于 a[i]
。对于数组的第一个成员,
*(a + 0)
(即 *a
)等同于 a[0]
。
由于数组名与指针是等价的,所以下面的等式总是成立。
1 |
|
上面代码给出了数组成员的两种访问方式,一种是使用方括号
a[b]
,另一种是使用指针 *(a + b)
。
如果指针变量 p
指向数组的一个成员,那么 p++
就相当于指向下一个成员,这种方法常用来遍历数组。
1 |
|
同一个数组的两个成员的指针相减时,返回它们之间的距离
1 |
|
C 流程控制
break 命令只能跳出循环体和 switch 结构,不能跳出 if 结构。
1 |
|
函数
main()
C 语言规定, main()
是程序的入口函数,即所有的程序一定要包含一个 main()
函数。程序总是从这个
函数开始执行,如果没有该函数,程序就无法启动。其他函数都是通过它引入程序的。
函数指针
对于任意函数,都有五种调用函数的写法。
1 |
|
struct 结构
C 语言内置的数据类型,除了最基本的几种原始类型,只有数组属于复合类型,可以同时包含多个值, 但是只能包含相同类型的数据,实际使用中并不够用。
实际使用中,主要有下面两种情况,需要更灵活强大的复合类型。
- 复杂的物体需要使用多个变量描述,这些变量都是相关的,最好有某种机制将它们联系起来。
- 某些函数需要传入多个参数,如果一个个按照顺序传入,非常麻烦,最好能组合成一个复合结构传 入。
为了解决这些问题,C 语言提供了 struct
关键字,允许自定义复合数据类型,将不同类型的值组合在一
起。这样不仅为编程提供方便,也有利于增强代码的可读性。C
语言没有其他语言的对象(object)和
类(class)的概念,struct
结构很大程度上提供了对象和类的功能。
下面是 struct 自定义数据类型的一个例子。
1 |
|
上面示例定义了一个分数的数据类型 struct fraction ,包含两个属性 numerator 和 denominator 。
注意,作为一个自定义的数据类型,它的类型名要包括 struct 关键字,比如上例是 struct fraction ,单独的 fraction 没有任何意义,另外, struct 语句结尾的分号不能省略
定义了新的数据类型以后,就可以声明该类型的变量,这与声明其他类型变量的写法是一样的。
1 |
|
1 |
|
struct
的数据类型声明语句与变量的声明语句,可以合并为一个语句。
1 |
|
指针变量也可以指向 struct 结构。
1 |
|
struct 指针
如果将 struct 变量传入函数,函数内部得到的是一个原始值的副本
1 |
|
上面示例中,函数 happy()
传入的是一个
struct
变量 myTurtle
,函数内部有一个自增操作。但是,执行完 happy()
以后,函数外部的 age
属性值根本没变。原因就是函数内部得到的是 struct
变量的副本, 改变副本影响不到函数外部的原始数据
通常,希望传入函数的是同一份数据,函数内部修改数据以后,会反映在函数外部。而且,传入的是同一份数据,也有利于提高程序性能。这时就需要将
struct
变量的指针传入函数,通过指针来修改
struct
属性,就可以影响到函数外部。
struct
指针传入函数的写法如下:
1 |
|
函数内部也必须使用 (*t).age
的写法,从指针拿到
struct
结构本身:
1 |
|
上面示例中, (*t).age
不能写成 *t.age
,因为点运算符 .
的优先级高于 *
。
*t.age
这种写法会将 t.age
看成一个指针,然后取它对应的值,会出现无法预料的结果。
现在,重新编译执行上面的整个示例, happy()
内部对
struct
结构的操作,就会反映到函数外部。
(*t).age
这样的写法很麻烦。C
语言就引入了一个新的箭头运算符->
,可以从
struct
指针上直接 获取属性,大大增强了代码的可读性。
1 |
|
总结一下,对于 struct
变量名,使用点运算符.
获取属性;对于 struct
变量指针,使用箭头运算符
->
获取属性。
以变量 myStruct
为例,假设 ptr
是它的指针,那么下面三种写法是同一回事。
1 |
|
指针
简介
指针是什么?首先,它是一个值,这个值代表一个内存地址,因此指针相当于指向某个内存地址的路标。
字符 *
表示指针,通常跟在类型关键字的后面,表示指针指向的是什么类型的值。比如,
char*
表示一 个指向字符的指针, float*
表示一个指向 float
类型的值的指针。
1 |
|
星号 * 可以放在变量名与类型关键字之间的任何地方,下面的写法都是有效的
1 |
|
& 运算符与 * 运算符互为逆运算,下面的表达式总是成立。
1 |
|
指针变量的初始化
声明指针变量之后,编译器会为指针变量本身分配一个内存空间,但是这个内存空间里面的值是随机 的,也就是说,指针变量指向的值是随机的。这时一定不能去读写指针变量指向的地址,因为那个地址 是随机地址,很可能会导致严重后果。
1 |
|
上面的代码是错的,因为 p 指向的那个地址是随机的,向这个随机地址里面写入 1 ,会导致意想不到的 结果。 正确做法是指针变量声明后,必须先让它指向一个分配好的地址,然后再进行读写,这叫做指针变量的 初始化。
1 |
|
上面示例中, p 是指针变量,声明这个变量后, p
会指向一个随机的内存地址。这时要将它指向一个已
经分配好的内存地址,上例就是再声明一个整数变量 i ,编译器会为 i
分配内存地址,然后让 p 指向 i 的内存地址( p = &i;
)。完成初始化之后,就可以对 p 指向的内存地址进行赋值了(
*p = 13;
)。
typedef 命令
typedef
命令用来为某个类型起别名。
1 |
|
比如下面的:
1 |
|
INTEGER a, b;
等效于int a, b;
。
typedef
可以一次指定多个别名
1 |
|
typedef
可以为指针起别名。
1 |
|
typedef
为结构体类型定义别名:
1 |
|
STU 是 struct stu 的别名,可以用 STU 定义结构体变量:
1 |
|
它等价于:
1 |
|
typedef
也可以用来为数组类型起别名。
1 |
|
typedef
为函数起别名的写法如下。
1 |
|
主要好处
(1)更好的代码可读性。
1 |
|
(2)为 struct、union、enum 等命令定义的复杂数据结构创建别名,从而便于引用。
1 |
|
typedef
也可以与 struct
定义数据类型的命令写在一起
1 |
|
这种情况下,C 语言允许省略 struct 命令后面的类型名。
1 |
|
(3)typedef 方便以后为变量改类型。
1 |
|
上面示例中,变量 f1 、 f2 、 f3 的类型都是 float
。如果以后需要为它们改类型,只需要修改 typedef
语句即可。
1 |
|
上面命令将变量 f1 、 f2 、 f3 的类型都改为 long double
。
typedef 和 #define 的区别
typedef 在表现上有时候类似于 #define,但它和宏替换之间存在一个关键性的区别。正确思考这个问题的方法就是把 typedef 看成一种彻底的“封装”类型,声明之后不能再往里面增加别的东西。
- 可以使用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做。如下所示:
1 |
|
- 在连续定义几个变量的时候,typedef 能够保证定义的所有变量均为同一类型,而 #define 则无法保证。例如:
1 |
|
经过宏替换以后,第二行变为:
1 |
|
这使得 p1、p2 成为不同的类型:p1 是指向 int 类型的指针,p2 是 int 类型。
相反,在下面的代码中:
1 |
|
p1、p2 类型相同,它们都是指向 int 类型的指针。
其他
如果在int
型变量的声明中为变量赋应该实数值的初始值(如3.14或5.7等)会怎么样?
- 会直接舍去小数部分,只保留整数部分
单目运算符&(取值运算符)
- &a,取得a的地址(生产指向a的指针)
单目运算符*(指针运算符)
- *a,a指向的对象
赋值表达式的左操作数不可以是数组名。
p
指向x
时,*p
是x
的别名
指针p
指向数组中的元素e
时
p + i
为指向元素e
后第i
个元素的指针p - i
为指向元素e
前第i
个元素的指针- 指向元素
e
后第i
个元素的*(p + i)
,可以写为p[i]
- 指向元素
e
前第i
个元素的*(p - i)
,可以写为p[-i]
c
语言编译阶段出现如下的问题:
可以检查一下是不是有正在运行的端口没关闭!!!