Chapter 8
-
⚠️数组名和指针常量相似,但并不是指针!(也因为是指针常量,所以数组名也是un-assignable的)
- 编译器用数组名来记住数组的属性,只有当数组名在表达式中被使用时,编译器才为它产生一个指针常量
-
只有两种情况下数组名不用指针常量来表示:
- 用于
sizeof
:对数组名使用sizeof
将返回整个数组的长度(以字节为单位) - 用于
&
取址:得到指向数组的指针(而不是指向第一个元素的指针的指针)
- 用于
- 下标引用可以作用于任何指针,而不仅仅是数组名,因此C编译器不检查下标的范围,下标也可以为负
-
通过指针来访问数组中的元素可能比用下标的效率更高,因为通过下标访问数组总是要做以下运算:
arr + (index * sizeof(elementOfArr)) // 此处的乘法无法避免
而某些情况(主要是循环体)下,对该偏移值的计算将被编译器优化为纯加法(一个在现代基本没什么用的知识。。):
for (int* arrPtr = arr; arrPtr < arr + 10; arrPtr ++) { ... } // arrPtr++ 所加上的值只需要计算第一次
- 一个注意:
指针不仅在做加法时需要考虑元素大小,两指针相减的结果也会被通过除法转换为元素个数(单纯比较是否相等则不需要)
-
⚠️函数接收多维数组做参数时,必须指定除第一维外其他所有维度的大小,否则寻址时函数无法计算偏移量
void func(int x, int y, int arr[][]) { // 未指定第二维大小 printf("%d", arr[x][y]); // 访问arr的第(x * sizeOfRow + y)个元素,但函数不知道sizeOfRow }
-
字符数组的初始化与字符串常量
char msg[] = "Hello"; char *msg = "Hello"; // 字符串常量是read only的
-
⚠️声明一个指向数组的指针:
int arr[3][10]; int (*arrPtr)[10] = arr; // arrPtr是一个指向拥有10个int元素的数组的指针,arrPtr + 1将指向下一行的10个int的开始
多维数组可以通过以下形式传递给函数:
void func(int (*mat)[10]); void func(int mat[][10]);
但当多维数组各维度的大小并不总固定时(我的最爱~),可以通过以下方式将它以一维数组的形式传递给函数:
int *arrPtr = &arr[0][0]; int *arrPtr = arr[0];
但是以下这种声明可能会导致严重的问题:
int arr[3][10]; int (*arrPtr)[] = arr; // 此时指针arrPtr不知道数组arr的size信息,它将会把0当作arr的二维宽度(而不是3)
-
下标引用
[]
的优先级高于间接访问*
int *p[10]; // p被声明为拥有10个int*元素的数组
Chapter 9
-
strlen
的返回值为size_t
,是一个unsigned值(永远大于等于0) - 标准库中的字符串函数不负责安全性检查,程序员必须保证字符串以
NULL
结尾(在必要的情况下)且大小合适 -
strcpy
、strcat
等函数会将目标字符串的地址作为返回值,这个特性令它们可以被嵌套使用 - 即使是对转换大小写这种简单的操作,使用库函数也有利于提高程序在使用不同字符集的平台间的移植性
-
strerror
函数接收一个错误代码,并返回一个指向描述该错误的字符串的指针
Chapter 10
-
⚠️
struct
(突然发现我根本不知道struct
的标准语法。。)struct Tag { // struct关键字和花括号之间的部分为该struct的标签,为可选部分,这样声明的结构体每次使用时都要加上struct关键字 int val; char name[10]; } tag1, *tag2; // 此处可以顺便声明该类型的变量,但此后声明新变量必须使用struct Tag tag;,因为Tag实际上不是一种类型 typedef struct { // 将这种struct定义为一种类型,因此以后使用时不用加struct关键字 int val; char name[10]; } Tag; // 以后可以直接使用Tag tag;来声明新变量
所以
struct {};
才是一个整体,它可以被加上标签作为记号,也可以被typedef
为一种新的结构体(我竟然一直以为是在主函数外声明新变量就要在类型前加struct关键字。。所以我这么久都写的啥==)
-
点操作符
.
的优先级高于间接访问*
,所以C提供了->
来更为方便地通过结构体指针访问它的成员(原来如此!!)(*ptr).val = 0; ptr->val = 0; // 显然下面这种写法更为直观
以及优先级上
->
高于.
高于*
:*p->a.b; // 将会从p指向的结构体中取出a结构体的b成员并对它进行间接访问,即*((p->a).b)
-
不完整声明:使两个(或更多)结构体可以互相包含对方的指针
struct B; // 提前声明B的存在 struct A { struct B *bPtr; ... }; struct B { struct A *aPtr; ... };
- 从C2011开始
typedef struct foo foo;
是合法的了,但是这种写法在之前的编译器上(可能!)会出现兼容性问题 -
⚠️C中的空结构体为undefined行为(但C++允许结构体为空),以及结构体实际上允许嵌套声明,因此可能无意间声明空结构体:
struct A { struct B { // 此处实际只是struct B的定义,而非变量声明。因此没有其他成员的struct A实际为空结构体 int id; ... }; };
- ⚠️对结构体使用
sizeof
将返回它实际占用的字节数,包括那些为了边界对其而跳过的字节。因此要确定一个成员对于结构体起始位置的实际偏移量,应使用
offset(type, member)
宏(位于stddef.h
),type
为结构类型,member
为该成员的名字。这个宏将返回一个size_t
值 - 把整个结构体作为函数的参数or返回值都会导致整个结构体被复制,使用指针效率更高
-
位段(bit filed):(本质上是不可移植的。。hmmm)
- 位段成员必须为
int
或unsigned int
类型 - 成员后跟一个冒号和一个用于指定该位段所占用的bit数的整数
struct CHAR { unsigned ch : 7; unsigned font : 16; unsigned size : 19; }; struct CHAR c1;
- 位段成员必须为
-
⚠️联合(
union
):(通过访问不同的联合成员,内存中的同一块内容可以被解释为不同的东西)-
与
struct
相似,union
也允许嵌套声明struct POINTER { // 实际包含两个变量,type和ptr enum {INT, FLOAT, STRING} type; union { int *i; float *f; char *s; } ptr; // 匿名union };
- 联合中所有成员都拥有相同的起始地址,整个联合的大小取决于最大的成员
- 为了避免个别超长成员造成空间浪费,通常只储存指向不同成员的指针,使用中再为该成员动态分配内存
-
初始化:初始值必须为第一个成员的类型,且必须用花括号包围
union { int a; float b; } x = { 5 }; // 其他类型的初始值(如果可能的话)将会被转换为一个int赋值给x.a
-
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。