C语言知识巩固

总览

常数的数据类型

#define 1234            // default type, int
#define 123456l // long

#define 1234u // unsigned int
#define 123456ul // unsigned long

#define 123.4f // float
#define 1e-2f // float
#define 123.4 // default type, double
#define 1e-2 // default type, double

字符和字符串

#define X    'x'        // 直接使用字母, 会自动转换为ASCII编码
#define CR '\015' // 八进制表示CR, 以\0开头
#define CR '\13' // 十进制表示CR, 以\开头
#define CR '\x0D' // 十六进制表示CR, 以\x开头
#define CR '\r' // 特殊表示法

// 字符串, 独立使用多个双引号, 也表示一串字符串
char short_str[] = "hello," " world" "!";
char long_str[] = "this is a very very long string,"
"so you can set it in multi lines "
"by this way"

优先级

# 1. == 和 != 优先级高于位操作符 & | ^
if ((x & MASK) == 0) // (x & MASK) 括号不能省!
if ( x & y == 0) // 含义为 x & (y==0)

# 2. 赋值运算符优先级最低 (只比逗号高一级别)
if ((c = getchar()) != EOF) // c获得输入值, 然后判断是否是结束符
if (c = getchar() != EOF) // 含义为 c = (getchar() != EOF), 所以c的赋值为 TRUE 或者 FALSE

# 3. && 的优先级 高于 ||
if ((cond1 || cond2) && cond3) // 先或再与
if (cond1 || cond2 && cond3) // 含义为 cond1 || (cond2 && cond3)

# 4. 算数运算优先级高于位移运算
(msb << 4) + lsb // 先位移, 再做加法
msb << 4 + lsb // 含义为 msb << (4 + lsb)

# 5. 先->/./&, 然后++/--, 最后 *,
++p->len // ++(p->len), len值++
p->len++ // (p->len)++, len值++
(++p)->len // 先加p的值, 然后取len值
p++->len // (p++)->len 先加p的值, 然后取len值
p->++len // p->(++len) 语法错误, 不知道len是什么!

++*p // ++(*p), 内容++
(*p)++ // 内容++
*p++ // *(p++), 地址++, 然后取内容

++*p->str // ++(*p->str). str指向的内容++
(*p->str)++ // str指向的内容++
*p->str++ // *(p->str), (p->str)++. 取出str的内容, 然后str地址++
*p++->str // *p->str, p++. 取出str的内容, 然后p++
*++p->str // *(++p->str). str地址++, 然后取str的内容
*p->++str // *p->(++str) 语法错误, 不知道str是什么!

++*p.len // ++*(p.len), 多数情况下p是指针, 因此语法错误
++(*p).len // ++(p->len), str值++
(*p++).len // p++->len, p地址++, 然后取str值
优先级 运算符 说明 结合方向
1 后置 ++ -- 自增++ 自减– 左到右
   | `[]`                              | 数组下标                   |
   | `()`                              | (表达式)/函数名(形参表)    |
   | `.`                               | 对象.成员名                |
   | `->`                              | 对象指针->成员名           |

2 | - | 负号 | 右到左
| (类型) | 强制类型转换 |
| ++ -- 前置 | ++自增 –自减 |
| * | *指针表达式 |
| & | &左值表达式 |
| ! | !表达式 |
| ~ | ~表达式 |
| sizeof | sizeof 表达式/sizeof(类型) |
3 | * / % | 乘 除 取余 | 左到右
4 | + - | 加 减 | 左到右
5 | << >> | 左移 右移 | 左到右
6 | > >= < <= | 大于(等于) 小于(等于) | 左到右
7 | == != | 等于 不等于 | 左到右
8 | & | 按位与 | 左到右
9 | ^ | 按位异或 | 左到右
10 | l (竖杠) | 按位或 | 左到右
11 | && | 逻辑与 | 左到右
12 | ll (竖杠) | 逻辑或 | 左到右
13 | ?: | 表达式1? 表达式2: 表达式3 | 右到左
14 | = /= *= %= += -= | 赋值运算符 | 右到左
| <<= >>= &= ^= l= (竖杠) | 位赋值运算符 |
15 | , | 表达式,表达式,… | 左到右

关键字

// 以 const 为例, volitale是一样的.

const int; // int是const
int const; // int是const

const char*; // char是const
char const*; // char是const
char* const; // *(指针)是const

const char* const; // char和*都是const
char const* const; //char和*都是const
  • register, 建议编译器把该变量直接放到寄存器中, 而不是内存中. 以提高运算速度!
    • 注意, 只是建议, 因此编译器可以忽略这个关键字
    • 变量必须是能被CPU寄存器接收的数据类型, 如 int
    • 现代编译器的优化效率很高, 因此 register 很少使用.
    • 还有一点. 譬如 register int val; 这时 &val 会报错! 因为取的是内存地址, 而val是在寄存器内, 所以不存在内存地址!
  • auto, 老版本C用于声明这是一个局部变量(与static相反). 新版本用于声明一个自动类型的变量.
    • 这个关键字是真没人去用. 因为函数内默认就是局部变量.
    • 习惯写C的人, 基本习惯了先选好一个数据类型.

宏定义

// #, Stringfication! 将宏变量直接转变为字符串(加上双引号)
#define debug(v) printf(#v "=%x\n", v)

debug(value); // 宏展开为 printf("value" "=%x\n", value)
// 若value值为0xFF, 执行结果就是打印出: value=0xFF


// ##, Concatenator! 宏变量连接符, 多用于自动生成变量名
#define CAT(x,y) x##y
#define LINK(a,b,c) a##_##b##_##c

CAT(var, 12); // 宏展开为 var12
LINK(name,age,sex); // 宏展开为 name_age_sex

// __VA_ARGS__, 宏定义可变参数
#define DRV_DEBUG 1
#if DRV_DEBUG
#define DRV_PRINT(fmt, ...) printf(fmt, __VA_ARGS__)
#else
#define DRV_PRINT(fmt, ...)
#endif

// 编译器内置的宏定义:
__LINE__ // 在源代码中插入当前源代码行号;
__FILE__ // 在源文件中插入当前源文件名;
__DATE__ // 在源文件中插入当前的编译日期
__TIME__ // 在源文件中插入当前编译时间;
__STDC__ // 当要求程序严格遵循ANSI C标准时该标识被赋值为1;
__cplusplus // 当编写C++程序时该标识符被定义。

typedef 的用法

// 我习惯将所有typedef定义为形如 uint8_t, 即已 "_t" 结尾

typedef struct{
int x;
int y;
} point_t; // 数据结构

typedef int (*pfun_t)(int, int); // 函数

typedef char array80_t[80]; // 数组

指针

char *name[] = { "None", "Jan", "Feb", "Mar" };     // 指针数组 name
int *daytab[13]; // 指针数组 daytab
int (*daytab)[13]; // 数组指针 daytab, 譬如 int a[3][13], 则可以 p=a; p++后就指向了 &a[1][0]

int *f(); // f 函数的返回值为指针类型
int (*pf)(); // 函数指针 pf
// 这两个是极端复杂的例子, 实际代码建议先用 typedef 定义好一层, 然后再嵌套使用, 会容易理解的多!
char (*(*x[3])())[5]; // x: array[3] of pointer to function returning pointer to array[5] of char
// x,一个指针数组, 这些指针指向函数, 函数的返回值是数组指针...
char (*(*x())[])(); // x: function returning pointer to array[] of pointer to function returning char
// x,一个函数, 返回值为一个指针. 这个指针指向一个函数指针列表. 函数指针列表指向的函数是返回 char 类型的.

// 举个例子 ----->
#include <stdio.h>

char foo() { return 'a'; }
char bar() { return 'b'; }
char blurga() { return 'c'; }
char bletch() { return 'd'; }

char (*gfunclist[])() = {foo, bar, blurga, bletch};

char (*(*x())[])()
{
static char (*funclist[4])() = {foo, bar, blurga, bletch};
return &funclist;
}

int main()
{
printf("%c\n",gfunclist[0]());

char (*(*fs)[4])();
fs = x();
printf("%c\n",(*fs)[1]());
}

// 更容易读懂的版本 ----->
#include <stdio.h>

typedef char (*pfun_t)();

char foo() { return 'a'; }
char bar() { return 'b'; }
char blurga() { return 'c'; }
char bletch() { return 'd'; }

pfun_t gfunclist[] = {foo, bar, blurga, bletch};

pfun_t* x()
{
static pfun_t funclist[4] = {foo, bar, blurga, bletch};
return funclist;
}

int main()
{
printf("%c\n",gfunclist[0]());

pfun_t *fs;
fs = x();
printf("%c\n",fs[1]());
}

结构体和位域

  • Bit-fields 位域, 一般不建议使用. 编译器相关且机器相关, 因此可移植性很差.
union {
int is;
struct {
unsigned int keyword : 1;
unsigned int extern : 1;
unsigned int static : 1;
} flags;
} bit_union;

printf 及 scanf

// 打印任意字符串的安全方法
printf(s); // FAILS if s contains %
printf("%s", s); // SAFE

// 调用可变参数的方法
void minprintf(char *fmt, ...)
{
va_list ap; /* points to each unnamed arg in turn */
va_start(ap, fmt); /* make ap point to 1st unnamed arg */

// do something ...
ival = va_arg(ap, int);
// do something ...

va_end(ap);
}

// 宏定义 printf
#define DRV_PRINT(fmt, ...) printf(fmt, __VA_ARGS__)


// scanf 避免溢出的方法
scanf("%s", buf); // 可能溢出!
scanf("%20s", buf); // 最多读取20个字符串

// 自己写个scanner.
int scanner(char *buffer, size_t buflen) {
char format[32];
if (buflen == 0) return 0;
snprintf(format, sizeof(format), "%%%ds", (int)(buflen-1));
return scanf(format, buffer);
}

参考资料


原创于 DRA&PHO