【笔记】C 小程杂记
八进制与十六进制
0
开头表示八进制。0x
开头表示 16 进制。 07 vaild 08 invalid 0xf vaild 0xg invaild%o
以八进制格式输出 /%x
以十六进制格式输出 (输出不带0
和0x
)%#o
以八进制格式输出 /%#x
以十六进制格式输出 (输出带0
和0x
)
设置输出宽度,可以在输出格式前加代表宽度的数字,比如
%14d
,表示输出宽度为14个字符;设置输出宽度后,默认为右对齐,要设置左对齐,需要加-号,即%-14d
。scanf("%3d%2d",&i,&j);
输入 1234567, i = 123, j = 45.原码反码补码
正数 - 原反补相同。
负数 - 原码:符号位为1 / 反码:除了符号位,全部取反 / 补码:反码+1. e.g. char (-10): (10: 00001010) 原 10001010 反 11110101 补 11110110
在计算机中,所有的整数使用补码计算与存储。
sizeof()
中的运算不会被进行。int k = 3; sizeof(++k);
k 的值仍是 3。scanf 和 gets 的区别
开头:scanf 忽略开头的所有空格。gets 不会忽略。
scanf 的结尾:scanf 遇到空格或者回车都会结束。同时,scanf 会将使其结束的空格(回车)留下来留给下一次读入。scanf 还会在最后加一个
\0
。gets 的结尾:gets 遇到回车才会结束。同时,gets 会将使其结束的回车吃掉并将这个
\n
替换为\0
。
(也就是说,两种读入方式均以 \0
结尾)
字符数组、字符串常量、字符指针
两种方式均可以表示字符串。
1char str1[5] = "fuck"; /* 字符数组,并对其进行了初始化。*/ 2char *str2 = "shit"; /* 字符指针 str2 指向了一个字符串常量的首地址, 3字符串常量在内存中的位置由系统自动安排。*/
这两种声明方式下,
str1
和str2
均是指向 首地址f/s
的指针。字符 / 字符串的输出
用 %s 可以输出从指针地址到第一个
'\0'
间的所有字符。用 %p 就是单纯地输出一个指针地址。
用 %c 并解引用就是输出指针地址指向的那个字符。
1#include <stdio.h> 2int main() 3{ 4 char *str = "fuckoff"; 5 6 printf("%s\n", str); // fuckoff 7 printf("%p\n", str); // 0040b044 8 printf("%c\n", *str); // f 9 10 printf("%s\n", str + 1); // uckoff 11 printf("%p\n", str + 1); // 0040b045 12 printf("%c\n", *(str + 1)); // u 13}
只读性
1char str1[5] = "fuck"; 2char *str2 = "shit";
需要注意的是,和 int 数组一样,
str1
将是一个只读的指针变量。我们不能++str1
。但是我们可以
++str2
。之后printf("%s\n", str2);
的结果为hit
。本质上是改变了str2
的地址。不过,对于第一种声明方式,字符数组中的字符可以改变。比如我们可以
str1[2] = '\0'
,这之后printf("%s\n", str1);
的结果为fu
。对于第二种声明方式,由于是指向字符串常量,其中的字符无法改变。
*(str2 + 2) = 'a';
和str2[2] = 'a';
都是不合法的。但如果str2
指向的是一个字符数组的话,就可以改变了。例如1#include <stdio.h> 2int main() { 3 char str[] = "fuckoff"; 4 char *p = "hello"; 5 p = str; 6 printf("%s\n", str + 2); // ckoff 7 *(p + 3) = 'z'; 8 printf("%s\n", p + 2); // czoff 9 return 0; 10}
假设 p 是一个指针。
p = NULL;
和p = 0;
都代表将 p 赋为空指针。动态内存申请
malloc 函数:
void* malloc(unsigned size)
申请长度为 size 的内存空间并返回首地址。申请失败返回 NULL。calloc 函数:
void* calloc(unsigned n, unsigned size)
分配 n 个连续空间,每一存储空间的长度为 size,并且初始化值全部为 0。申请成功返回首地址。申请失败返回 NULL。free 函数:
void free(*p)
释放由动态存储分配函数(如上)申请到的整块内存空间。p 为指向要释放空间的首地址。realloc 函数:
void* ealloc(*p, unsigned size)
更改以前的内存分配。p 是之前由动态内存分配得到的指针,size 为现在需要的空间大小。分配失败保留原空间并返回 NULL。分配成功返回一片大小为 size 的区块,并保证该块的内容与原块的一致。注意,如果分配成功,原存储块的内容可能改变并不再允许使用。为什么 malloc, calloc, realloc 函数有返回值却是 void 类型?因为这是一种 通用指针,即它的返回值的类型是不确定的。当我们需要某种特定类型的返回值时,直接在前面用
(data type)
进行强制类型转换即可。(参见链表动态存储空间的申请)
Pointer More
数组指针
数组指针本质上是一个指向数组的指针变量。例如:
1#include <stdio.h> 2 3int main() { 4 int a[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; 5 int (*num)[4]; 6 num = &a[1]; 7 printf("%d\n", *(*num + 1)); // output 6. 8 return 0; 9}
指针数组
通过
char *str[5];
可以声明一个含有 5 个元素的数组。每个元素的类型都是字符指针。指针数组的名字也是一个二级指针。比如上述例子中,
str
本质上也是一个二级指针。此外,指针数组还可以进行类似数组的加法操作。下面的例子中,
p[3][2]
等价于*(p[3] + 2)
。(就像一维数组*(a + 2)
等价于a[2]
一样)1#include <stdio.h> 2int main() { 3 int i, a[12] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; 4 int *p[4]; 5 for (i = 0; i < 4; i++) p[i] = &a[i * 3]; 6 printf("%d\n", p[3][2]); 7 return 0; 8} // PTA 原题,输出 12。
二级指针、二维数组与指针
1int a = 10; 2int *p = &a; 3int **p = &p;
上述代码中,p 为 a 的地址,pp 为 p 的地址。
二维数组与指针:
int a[3][4];
可以看成由a[0], a[1], a[2]
作为元素组成的一维数组(每个元素本身又是一个长度为 4 的一维数组)。a[0]
是一个一级指针,指向数组a[0]
的首地址,即&a[0][0]
。而a
则为一个二级指针,指向首元素的地址&a[0]
。推广地,
a + 1
表示第一行的地址。*(a + 1)
表示第一行这个元素,也代表第一行首元素的地址。进而,**(a + 1)
表示第一行的首元素,亦即a[1][0]
的值。*(*(a + i) + j)
等价于a[i][j]
。指向函数的指针
例如
int (*funcp)(int, int)
定义了一个函数指针 funcp。它可以指向任意有两个整型参数并且返回值为 int 类型的函数。假设函数
func(x, y)
已经定义,那么funcp = func;
就可以将 func 的 入口地址 赋给 funcp(类比:数组名本质上也是指针),funcp 于是指向了函数func(x, y)
。然后我们就可以这样调用函数:
(*funcp)(1, 2);
。链表
链表中每个节点由数据部分和下一个结点的地址构成。链表变量一般用指针
head
表示,用来存放链表首节点的地址。链表中的最后一个节点称为表尾,其下一个节点的地址为 NULL(注意表尾节点的数据部分仍是有效的)。链表的声明
通常借用 结构体嵌套 的方式来定义一个链表。
1struct node { 2 int info1, info2, info3; 3 struct node* next; // 结构的递归定义 4};
info 是这个节点的数据部分。next 则指向了下一个节点。
链表是动态存储分配的数据结构
比如,如果想要申请 node 结构的动态内存空间,需要使用以下语句。
1struct node* p; 2p = (struct node*)malloc(sizeof(struct node); // 需要强制转换
Basic Operations
insert pn between p1 & p2
1p1 -> next = pn; 2pn -> next = p2;
delete p2 (p1 points to p2)
1p1 -> next = p2 -> next; 2free(p2);
Struct
关于 struct 的优先级问题:
->
大于.
大于*
大于++(--)
。关于 C 语言中 struct 的初始化:
1#include <stdio.h> 2struct A { 3 int x, y; 4}; 5int main() { 6 struct A a[2] = { {1, 2}, {3, 4} }; 7 printf("%d %d %d %d\n", a[0].x, a[0].y, a[1].x, a[1].y); 8 // Output: 1 2 3 4 9}
可以采取如上的初始化方式。
但是需要注意,这种初始化方式必须在声明时使用。以下语句段是不合法的。
1struct A a[2]; 2a[0] = {1, 2}; a[1] = {3, 4};
但如果采用以下方式,又是可以的了。
1struct A a[2]; 2a[0] = (struct A){1, 2}; a[1] = (struct A){3, 4}; 3// 这个相当于是把一个临时定义的 struct 赋到了 a[0] 和 a[1] 上。
Switch
case 后 只能为常量或常量表达式 。switch 支持的变量类型有 byte、short、int、char。
循环体内的 switch 可以使用 continue。此时 continue 作用于循环体本身,即(执行 i++ 后)跳到下一次循环。