《C和指针》读书笔记

1. 三字母词

    三字母词即用三个字符合起来表示另一个字符,它可以使C环境在某些缺少一些必需字符的字符集上实现。

??( [    ??< {    ??= #
??) ]    ??> }    ??/ 
??! |    ??'  ^    ??- ~

2. 转义字符

ddd  表示由八进制数ddd的数值所代表的字符
xddd 表示由十六进制数ddd的数值所代表的字符

3. 字面值前/后缀

(1) 在多字节字符常量前面添加L,则它是宽字符常量。如:L'X', L'e^'等。

(2) 在整数字面值后面添加L或l,可以使这个整数被解释为long型;添加U或u则用于把数值指定为unsigned型;两者均添加则被解释为unsigned long型。

4. 当for/while循环体无事可做时,单独用一行来表示一条空语句是个比较好的做法,这样清晰地表明循环体是空的,不至于使人误会接下来的一条语句才是循环体。

while ((ch = getchar()) != EOF && ch != '
')
    ;  // 单独用一行来表示空语句是种好做法。

5. 如果你可以互换地使用指针表达式和下标表达式时,应该使用哪一个?从可读性上看,下标显然更好;从效率上看,下标绝不会比指针更有效率,但指针有时会比下标更有效率。

// use array's index
int arr[10], i;
for (i = 0; i < 10; ++i)
    arr[i] = 0; // 每次循环时都要计算i * 4

// use pointer
int arr[10], *ptr;
for (ptr = arr; ptr < arr + 10; ++ptr) // 只需在编译时计算一次1*4,运行时不执行乘法
    *ptr = 0; 

6. 字符串查找

char *strchr(char const *str, int ch);  // 寻找字符ch第一次出现的位置
char *strrchr(char const *str, int ch); // 寻找字符ch最后一次出现的位置
char *strpbrk(char const *str, char const *group); // 寻找一组字符中的任意一个第一次出现的位置
char *strstr(char const *s1, char const *s2); // 寻找子字符串s2第一次出现的位置
size_t strspn(char const *str, char const *group); // 寻找字符串前缀
size_t strcspn(char const *str, char const *group ); // 寻找非字符串前缀
char *strtok(char *str, char const *sep); // 分隔并提取字符串

7. 字符分类

isspace(char ch); // 空白字符:' ', 'f', '
','
', '	', 'v'
isxdigit(ch); // 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
isalpha(ch); // 字母
isalnum(ch); // 字母或数字
ispunct(ch); // 标点符号,任何不属于数字或字母的可打印符号

8. 内存操作

void *memcpy(void *dst, void const *src, size_t len);
void *memmove(void *dst, void const *src, size_t len);
void *memcmp(void const *a, void const *b, size_t len);
void *memchr(void const *a, int ch, size_t len);
void *memset(void *a, int ch, size_t len);

9. 位域(bit field)

(1) 优点:节省存储空间;可以很方便地访问一个整型值的部分内容。

(2) 缺点:可移植性较弱:(a) int位域被当作有符号数还是无符号数,不能确定;(b) 位域中位的最大数目不确定。许多编译器把位域成员的长度限制在MAX_INT内,但32位的机器和16位的机器的MAX_INT值不同。(c) 当一个声明指定了两个位域,且第2个位域比较大以致无法容纳于第1个位域剩余的位时,编译器有可能把第2个位域放在内存的下1个字,也可能直接放在第1个位域后面,从而在两个内存位置的边界上形成重叠。

10. 动态分配内存

    只释放一块动态分配的内存的一部分是不允许的,动态分配的内存必须整块一起释放。如果要缩小一块动态分配的内存,应该使用realloc。

pi = (int *)malloc(10 * sizeof(int));
...
free(pi + 5);  // error! you can't free only part of the memory.

11. 函数指针的应用

(1) 回调函数。用户把一个函数指针作为参数传递给其他函数,后者将“回调”用户的函数。当你编写的函数必须能够在不同的时刻执行不同类型的工作或执行职能由函数调用者定义的工作时,你都可以使用这个技巧(此时往往需要把参数类型声明为void *)。许多窗口系统使用回调函数连接多个动作,如拖拽鼠标和点击按钮来指定用户程序中的某个特定函数。

(2) 转移表。适用于对不同的输入调用不同的函数的场合。

// 袖珍式计算器
double add(double, double);
double sub(double, double);
double mul(double, double);
double div(double, double);
...
double (*oper_func[])(double, double) = {add, sub, mul, div, ...};
result = oper_func[oper](op1, op2);

12. 字符串常量

    当一个字符串常量出现在表达式中,它的值是个指针常量。编译器把这些指定字符的一份拷贝存储在内存的某个位置,并存储一个指向第一个字符的指针。因此,类似数组名,我们可以对字符串常量进行下标引用、间接访问以及指针运算。

"xyz" + 1; // 结果为一个指针,指向字符'y'
*"xyz"; // 结果为字符'x'
"xyz"[2];  // 结果为字符'z'
printf("%s
", "**********" + 10 - n); // 打印n个星号*,注意0 <= n <= 10

13. 预定义符号

     

14. 宏和函数的区别

     

15. 输入/输出函数

(1) 错误报告

void perrno(char const *message);  // <stdio.h>

(2) 终止执行

    注意exit与return不同,return是退出当前函数,exit是退出整个程序。

void exit(int status);  // <stdlib.h>

(3) I/O总览

    

(4) 文件流

FILE *fopen(char const *name, char const *mode);  // 打开文件,失败时返回NULL
int fclose(FILE *f); // 关闭文件,成功时返回0,失败时返回非零值

(5) 字符I/O

// 读入字符
int fgetc(FILE *stream); // 函数
int getc(FILE *stream);  // 函数或宏
int getchar(); // 从标准流stdin中读入字符

// 输出字符
int fputc(int c, FILE *stream); // 函数
int putc(int c, FILE *stream); // 函数或宏
int putchar(int c); // 向标准流stdout输出字符

// 撤销字符
int ungetc(int c, FILE *stream); // 把一个先前读入的字符返回到流中

(6) 行I/O

// 输入字符串
char *fgets(char *buf, int buf_size, FILE *stream); 
char *gets(char *buf);  // 从标准输入流stdio中读入字符串

// 输出字符串
int fputs(char const *buf, FILE *stream);
int puts(char const *buf); // 向标准输出流stdout输出字符串

(7) 格式化的行I/O

// 格式化输入
int scanf(char const *format, ...);  // 从标准输入流stdio中读入
int sscanf(char const *str, char const *format, ...); // 从字符串str中读入
int fscanf(FILE *stream, char const *format, ...); // 从文件流stream中读入

// 格式化输出
int printf(FILE *stream, char const *format, ...); // 输出到标准输出流stdout
int sprintf(char *buf, char const *format, ...); // 输出到字符串buf
int fprintf(FILE *stream, char const *format, ...); // 输出到输出流stream

(8) 刷新和定位函数

int fflush(FILE *stream); //刷新文件流,清空缓冲区。
long ftell(FILE *stream); // 返回流的当前位置
int fseek(FILE *stream, long offset, int from); // 定位流的位置

(9) 改变缓冲方式

void setbuf(FILE *stream, char *buf); //设置数组buf,用来对流进行缓存
int setvbuf(FILE *stream, char *buf, int mode, size_t size); // mode用来指定缓冲的类型(完全缓冲、行缓冲、不缓冲)

(10) 流错误函数

int feof(FILE *stream); // 流处于文件尾时返回真
int ferror(FILE *stream); // 出现读写错误时返回真
void clearerr(FILE *stream); // 重置流的错误标志

(11) 文件操纵函数

int remove(char const *filename); // 删除文件
int rename(char const *oldname, char const *newname); // 重命名文件

16. 运行时环境

(1) 堆栈帧。函数存储变量和其他值的地方。

(2) 一个函数分成三个部分:函数序(prologue),函数体(body),函数跋(epilogue)三部分。函数序用于执行函数启动需要的一些工作,例如为局部变量保留堆栈中的内存。函数跋用于在函数即将返回之前清理堆栈。

原文地址:https://www.cnblogs.com/wuhualong/p/ReadingNote_Pointers_On_C.html