C 语言学习 小记
标签(空格分隔): c语言
在github上面看到别人一个很不错的学习笔记,记录一下其中的知识点
笔记by qyuhen
记录一下学习c语言遇到的一些知识点
数据类型
字符串
字符常量默认是⼀个 int 整数,但编译器可以⾃⾏决定将其解释为 char 或 int。
size('a') = 4
浮点数
- float: 32 位 4 字节浮点数,精确度 6。
- double: 64 位 8 字节浮点数,精确度 15。
- long double: 80 位 10 字节浮点数,精确度 19 位。
C99 提供了复数⽀持,⽤两个相同类型的浮点数分别表⽰复数的实部和虚部。
直接在 float、 double、 long double 后添加 _Complex 即可表⽰复数,在 complex.h 中定义了
complex 宏使得显⽰更统⼀美观
float complex size=8
double complex size=16
long double complex size=24
枚举
enum中的值可以自定义,之后的会依次递增,值可以相同
enum color { black, red = 5, green, yellow };
enum color b = black;
printf("black = %d\n", b);
...
black = 0
red = 5
green = 6
yellow = 7
----------------------------------------
enum color { black = 1, red, green = 1, yellow };
black = 1
red = 2
green = 1
yellow = 2
通常省略枚举⼩标签⽤来代替宏定义常量
----------------------------------------
enum { BLACK = 1, RED, GREEN = 1, YELLOW };
printf("black = %d\n", BLACK);
printf("red = %d\n", RED);
printf("green = %d\n", GREEN);
printf("yellow = %d\n", YELLOW);
字面值
整数常量
常量类型很重要,可以通过后缀来区分类型。
0x200 -> int
200U -> unsigned int
0L -> long
0xf0f0UL -> unsigned long
0777LL -> long long
0xFFULL -> unsigned long long
浮点常量
默认浮点常量是 double,可以⽤ F 后缀表⽰ float,⽤ L 后缀表⽰ long double 类型。
字符常量
字符常量默认是 int 类型,除⾮⽤前置 L 表⽰ wchar_t 宽字符类型。
字符串常量
C 语⾔中的字符串是⼀个以 NULL (也就是 \0) 结尾的 char 数组。
空字符串在内存中占⽤⼀个字节,包含⼀个 NULL 字符,也就是说要表⽰⼀个⻓度为 1 的字符串最少需要 2 个字节 (strlen 和 sizeof 表⽰的含义不同)。
wchar_t ws[] = L"中国⼈";
printf("len %d, size %d\n", wcslen(ws), sizeof(ws));
unsigned char* b = (unsigned char*)ws;
int len = sizeof(ws);
for (int i = 0; i < len; i++)
{
printf("%02X ", b[i]);
}
wchar_t 字符串以⼀个 4 字节的 NULL 结束
输出:
len 3, size 16
2D 4E 00 00 FD 56 00 00 BA 4E 00 00 00 00 00 00
编译器会⾃动连接相邻的字符串,这也便于我们在宏或者代码中更好地处理字符串。
#define WORLD "world!"
char* s = "Hello" " " WORLD "\n";
对于源代码中超⻓的字符串,除了使⽤相邻字符串外,还可以⽤ “\” 在⾏尾换⾏。
char* s1 = "Hello"
" World!";
char* s2 = "Hello \
World!";
类型转换
算术类型转换
在表达式中,可能会将 char、 short 当做默认 int (unsigned int) 类型操作数,但 float 并不会⾃动转换为默认的 double 类型。
当包含⽆符号操作数时,需要注意提升后类型是否能容纳⽆符号类型的所有值。
long a = -1L;
unsigned int b = 100;
printf("%ld\n", a > b ? a : b);
输出:
-1
输出结果让⼈费解。尽管 long 等级⽐ unsigned int ⾼,但在 32 位系统中,它们都是 32 位整数,
且 long 并不⾜以容纳 unsigned int 的所有值,因此编译器会将这两个操作数都转换为 unsigned
long,也就是⾼等级的⽆符号版本,如此 (unsigned long)a 的结果就变成了⼀个很⼤的整数。
可以显式将指针转换为整数,反向转换亦可
运算符
C99 新增的内容,我们可以直接⽤该语法声明⼀个结构或数组指针。
(类型名称){ 初始化列表 }
int* i = &(int){ 123 }; ! // 整型变量, 指针
int* x = (int[]){ 1, 2, 3, 4 }; ! // 数组, 指针
struct data_t* data = &(struct data_t){ .x = 123 }; ! // 结构, 指针
int* i = &(int){ 123 }; ! // 整型变量, 指针
int* x = (int[]){ 1, 2, 3, 4 }; ! // 数组, 指针
struct data_t* data = &(struct data_t){ .x = 123 }; ! // 结构, 指针
不要⽤ int 代替 size_t,因为在 32 位和 64 位平台 size_t ⻓度不同
逗号运算符
逗号是⼀个⼆元运算符,确保操作数从左到右被顺序处理,并返回右操作数的值和类型
int i = 1;
long long x = (i++, (long long)i);
printf("%lld\n", x);
语句
语句块
语句块代表了⼀个作⽤域,在语句块内声明的⾃动变量超出范围后⽴即被释放。除了⽤ “{…}” 表⽰⼀
个常规语句块外,还可以直接⽤于复杂的赋值操作,这在宏中经常使⽤。
int i = ({ char a = 'a'; a++; a; });
printf("%d\n", i);
选择语句
除了if...else if...else...
和 switch { case ... }
还有谁呢。
GCC ⽀持 switch 范围扩展。神奇!
int x = 1;
switch (x)
{
case 0 ... 9: printf("0..9\n"); break;
case 10 ... 99: printf("10..99\n"); break;
default: printf("default\n"); break;
}
char c = 'C';
switch (c)
{
case 'a' ... 'z': printf("a..z\n"); break;
case 'A' ... 'Z': printf("A..Z\n"); break;
case '0' ... '9': printf("0..9\n"); break;
default: printf("default\n"); break;
}
最后⼀个表达式被当做语句块的返回值
⽆条件跳转
⽆条件跳转: break, continue, goto, return。
goto 仅在函数内跳转,常⽤于跳出嵌套循环。如果在函数外跳转,可使⽤ longjmp。
5.4.1 longjmp
setjmp 将当前位置的相关信息 (堆栈帧、寄存器等) 保存到 jmp_buf 结构中,并返回 0。当后续代码执⾏ longjmp 跳转时,需要提供⼀个状态码。代码执⾏绪将返回 setjmp 处,并返回 longjmp所提供的状态码。
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <setjmp.h>
void test(jmp_buf *env)
{
printf("1....\n");
longjmp(*env, 10);
}
int main(int argc, char* argv[])
{
jmp_buf env;
int ret = setjmp(env); ! // 执⾏ longjmp 将返回该位置, ret 等于 longjmp 所提供的状态码。
if (ret == 0)
{
test(&env);
}
else
{
printf("2....(%d)\n", ret);
}
return EXIT_SUCCESS;
}
输出:
1....
2....(10)
函数
调用
C 函数默认采⽤ cdecl 调⽤约定,参数从右往左⼊栈,且由调⽤者负责参数⼊栈和清理.
int main(int argc, char* argv[])
{
int a()
{
printf("a\n");
return 1;
}
char* s()
{
printf("s\n");
return "abc";
}
printf("call: %d, %s\n", a(), s());
return EXIT_SUCCESS;
}
输出:
s a
call: 1, abc
可选性⾃变量
具体参考PDF
数组
可变长度数组
如果数组具有⾃动⽣存周期,且没有 static 修饰符,那么可以⽤⾮常量表达式来定义数组
void test(int n)
{
int x[n];
for (int i = 0; i < n; i++)
{
x[i] = i;
}
struct data { int x[n]; } d;
printf("%d\n", sizeof(d));
}
int main(int argc, char* argv[])
{
int x[] = { 1, 2, 3, 4 };
printf("%d\n", sizeof(x));
test(2);
return EXIT_SUCCESS;
}
初始化规则:
- 如果数组为静态⽣存周期,那么初始化器必须是常量表达式。
- 如果提供初始化器,那么可以不提供数组⻓度,由初始化器的最后⼀个元素决定。
- 如果同时提供⻓度和初始化器,那么没有提供初始值的元素都被初始化为 0 或 NULL。
初始化特定元素
int x[] = { 1, 2, [6] = 10, 11 };
int len = sizeof(x) / sizeof(int);
for (int i = 0; i < len; i++)
{
printf("x[%d] = %d\n", i, x[i]);
}
---------------------------------------
输出:
x[0] = 1
x[1] = 2
x[2] = 0
x[3] = 0
x[4] = 0
x[5] = 0
x[6] = 10
x[7] = 11
字符串
字符串是以 ‘\0’ 结尾的 char 数组, 这里体现在sizeof
char s[10] = "abc";
char x[] = "abc";
printf("s, size=%d, len=%d\n", sizeof(s), strlen(s));
printf("x, size=%d, len=%d\n", sizeof(x), strlen(x));
输出:
s, size=10, len=3
x, size=4, len=3
同样,我们可以初始化特定的元素.
int x[][2] =
{
{ 1, 11 },
{ 2, 22 },
{ 3, 33 },
[4][1] = 100,
{ 6, 66 },
[7] = { 9, 99 }
};
数组参数
参见PDF
指针
⾮⾃动周期指针变量或静态⽣存期指针变量必须⽤编译期常量表达式初始化,⽐如函数名称等
限定符
限定符 const 可以声明 “类型为指针的常量” 和 “指向常量的指针”
区别在于 const 是修饰 p 还是 *p
结构
结构类型⽆法把⾃⼰作为成员类型,但可以包含 “指向⾃⼰类型” 的指针成员
struct list_node
{
struct list_node* prev;
struct list_node* next;
void* value;
};
定义不完整结构类型,只能使⽤⼩标签,像下⾯这样的 typedef 类型名称是不⾏的。
typedef struct
{
list_node* prev;
list_node* next;
void* value;
} list_node;
----------------------------
//结合起来用
typedef struct node_t
{
struct node_t* prev;
struct node_t* next;
void* value;
} list_node;
----------------------------
//⼩标签可以和 typedef 定义的类型名相同。
typedef struct node_t
{
struct node_t* prev;
struct node_t* next;
void* value;
} node_t;
匿名结构
在结构体内部使⽤匿名结构体成员,也是⼀种很常⻅的做法
typedef struct
{
struct
{
int length;
char chars[100];
} s;
int x;
} data_t;
int main(int argc, char * argv[])
{
data_t d = { .s.length = 100, .s.chars = "abcd", .x = 1234 };
printf("%d\n%s\n%d\n", d.s.length, d.s.chars, d.x);
return EXIT_SUCCESS;
}
成员偏移量
利⽤ stddef.h 中的 ofsetof 宏可以获取结构成员的偏移量
typedef struct
{
struct
{
int length;
char chars[100];
} s;
int x;
} data_t;
int main(int argc, char * argv[])
{
data_t d = { .s.length = 100, .s.chars = "abcd", .x = 1234 };
printf("%d\n%s\n%d\n", d.s.length, d.s.chars, d.x);
return EXIT_SUCCESS;
}