C语言&嵌入式我爱编程C++

《C 语言程序设计:现代方法》复习查漏笔记

2017-09-18  本文已影响109人  gfson

版权声明:本文为 gfson 原创文章,转载请注明出处。
注:作者水平有限,文中如有不恰当之处,请予以指正,万分感谢。


第2章 C 语言基本概念

2.1. 字符串字面量(String Literal)

字符串字面量是用一对双引号包围的一系列字符。

2.2. 把换行符加入到字符串中

    printf("hello
    World\n");
    printf("hello "
    "World");

根据 C 语言标准,当两条或者更多条字符串字面量相连时(仅用空白字符分割),编译器必须把它们合并成单独一条字符串。这条规则允许把字符串分割放在两行或更多行中。

2.3. 编译器是完全移走注释还是用空格替换掉注释?

根据标准 C,编译器必须用一个空格符替换每条注释语句。
a/**/b = 0 相当于 a b = 0

2.4. 在注释中嵌套一个新的注释是否合法?

在标准 C 中是不合法的,如果需要注释掉一段包含注释的代码,可以使用如下方法:

    #if 0
    printf("hello World\n");
    #endif

这种方式经常称为「条件屏蔽」。

2.5. C 程序中使用 // 作为注释的开头,如下所示,是否合法?

// This is a comment.

在标准 C 中是不合法的,// 注释是 C++ 的方式,有一些 C 编译器也支持,但是也有一部分编译器不支持,为了保持程序的可移植性,因此应该尽量避免使用 //


第3章 格式化的输入/输出

3.1. 转换说明(Conversion specification)

转换说明以 % 开头,转化说明用来表示填充位置的占位符。

3.2. printf 中转换说明的格式

转化说明的通用格式为:

%m.pX 或者 %-m.pX

对于这个格式的解释如下:

3.3. %i 和 %d 有什么区别?

在 printf 中,两者没有区别。在 scanf 中,%d 只能和十进制匹配,而 %i 可以匹配八进制、十进制或者十六进制。如果用户意外将 0 放在数字的开头处,那么用 %i 代替 %d 可能有意外的结果。由于这是一个陷阱,所以坚持使用 %d。

3.4. printf 如何显示字符 %?

printf 格式串中,两个连续的 % 将显示一个 %,如下所示:

    printf("%%");

第4章 表达式

4.1. 运算符 /% 注意的问题

4.2. 由实现定义(implementation-defined)

4.3. 赋值运算符「=」

    int i;
    float f;

    f= i =33.6;

    printf("i=%d,f=%f",i,f);

4.4. 左值

4.5. 子表达式的求值顺序

C 语言没有定义子表达式的求值顺序(除了含有逻辑与运算符及逻辑或运算符、条件运算符以及逗号运算符的子表达式)。因此,在表达式 (a + b) * (c - d) 中,无法确定子表达式 (a + b) 是否在子表达式 (c - d) 之前求值。

为了避免此问题,最好不要编写依赖子表达式计算顺序的程序,一个好的建议是:不在字表达式中使用赋值运算符,如下所示:

  a = 5;
  b = a + 2;
  a = 1;
  c = b - a;

4.6. v += e 一定等价与 v = v + e 么?

不一定,如果 v 有副作用,则两者不想等。

4.7. ++ 和 -- 是否可以处理 float 型变量?

可以,自增和自减可以用于所有数值类型,但是很少使用它们处理 float 类型变量。如下所示:

    float f = 1.3;
    printf("%f",++f);

4.8. 表达式的副作用(side effect)

表达式有两种功能,每个表达式都产生一个值(value),同时可能包含副作用(side effect)。副作用是指改变了某些变量的值。如下所示:

  20          // 这个表达式的值是 20,它没有副作用,因为它没有改变任何变量的值。
  x=5         // 这个表达式的值是 5,它有一个副作用,因为它改变了变量 x 的值。
  x=y++       // 这个表达示有两个副作用,因为改变了两个变量的值。
  x=x++       // 这个表达式也有两个副作用,因为变量 x 的值发生了两次改变。

4.9. 顺序点(sequence point)

表达式求值规则的核心在于顺序点。


第5章 选择语句

5.1. 表达式 i < j < k 是否合法?

此表达式是合法的,相当于 (i < j) < k,首先比较 i 是否小于 k,然后用比较产生的结果 1 或 0 来和 k 比较。

5.2. 如果 i 是 int 型,f 是 float 型,则条件表达式 i > 0 ? i : f 是哪一种类型的值?

当 int 和 float 混合在一个表达式中时,表达式类型为 float 类型。如果 i > 0 为真,那么变量 i 转换为 float 型后的值就是表达式的值。


第7章 基本类型

7.1. 读 / 写整数

7.2. 转义字符(numeric escape)

在 C 语言中有三种转义字符,它们是:一般转义字符、八进制转义字符和十六进制转义字符。

7.3. 读字符的两种惯用法

while (getchar() != '\n') /* skips rest of line */
    ;
while ((ch = getchar()) == ' ') /* skips blanks */
    ;

7.4. sizeof 运算符

sizeof 运算符返回的是无符号整数,所以最安全的办法是把其结果转化为 unsigned long 类型,然后用 %lu 显示。

printf("Size of int: %lu\n", (unsigned long)sizeof(int));

7.5. 为什么使用 %lf 读取 double 的值,而用 %f 进行显示?


第11章 指针

11.1 指针总是和地址一样么?

通常是,但不总是。在一些计算机上,指针可能是偏移量,而不完全是地址

char near *p;      /*定义一个字符型“近”指针*/
char far *p;       /*定义一个字符型“远”指针*/
char huge *p;      /*定义一个字符型“巨”指针*/

近指针、远指针、巨指针是段寻址的 16bit 处理器的产物(如果处理器是 16 位的,但是不采用段寻址的话,也不存在近指针、远指针、巨指针的概念),当前普通 PC 所使用的 32bit 处理器(80386 以上)一般运行在保护模式下的,指针都是 32 位的,可平滑地址,已经不分远、近指针了。但是在嵌入式系统领域下,8086 的处理器仍然有比较广泛的市场,如 AMD 公司的 AM186ED、AM186ER 等处理器,开发这些系统的程序时,我们还是有必要弄清楚指针的寻址范围。

11.2. const int * p、int * const p、const int * const p


第12章 指针和数组

12.1. * 运算符和 ++ 运算符的组合

表达式 含义
*p++ 或 *(p++) 自增前表达式的值是 *p,然后自增 p
(*p)++ 自增前表达式的值是 *p,然后自增 *p
*++p 或 *(++p) 先自增 p,自增后表达式的值是 *p
++*p 或 ++(*p) 先自增 *p,自增后表达式的值是 *p

12.2. i[a] 和 a[i] 是一样的?

是的。
对于编译器而言,i[a] 等同与 *(i+a),a[i] 等同与 *(a+i),所以两者相同。

12.3. *a 和 a[]


第13章 字符串

13.1. 字符串字面量的赋值

char *p;
p = "abc";

这个赋值操作不是复制 "abc" 中的字符,而是使 p 指向字符串的第一个字符

13.2. 如何存储字符串字面量

13.3. 对指针添加下标

char ch
ch = "abc"[1];

ch 的新值将是 b。
如下,将 0 - 15 的数转化成等价的十六进制:

char digit_to_hex_char (int digit)
{
          return "0123456789ABCDEF"[digit];
}

13.4. 允许改变字符串字面量中的字符

char *p = "abc";
*p = 'b'; /* string literal is now "bbc" */

不推荐这么做,这么做的结果是未定义的,对于一些编译器可能会导致程序异常。

  • 针对 "abc" 来说,会在 stack 分配 sizeof(char *) 字节的空间给指针 p,然后将 p 的值修改为 "abc" 的地址,而这段地址一般位于只读数据段中。
  • 在现代操作系统中,可以将一段内存空间设置为读写数据、只读数据等等多种属性,一般编译器会将 "abc" 字面量放到像 ".rodata" 这样的只读数据段中,修改只读段会触发 CPU 的保护机制 (#GP) 从而导致操作系统将程序干掉。

13.5. 字符数组和字符指针

char ch[] = "hello world";
char *ch = "hello world";

两者区别如下:

13.6. printf 和 puts 函数写字符串

13.7. scanf 和 gets 函数读字符串

  • scanf 和 gets 函数都无法检测何时填满数据,会有数组越界的可能。
  • 使用 %ns 代替 %s 可以使 scanf 更安全,n 代表可以存储的最大字符数量。
  • 由于 gets 和 puts 比 scanf 和 printf 简单,因此通常运行也更快。

13.8. 自定义逐个字符读字符串函数

#include <stdio.h>
#include <stdlib.h>

int read_line(char[] ,int);

int main(void)
{
    char str[10];
    int n = 10;

    read_line(str, n);
    printf("--- end ---");
    return 0;
}

int read_line(char ch[], int n)
{
    char tmp_str;
    int i = 0;

    while((tmp_str = getchar()) != '\n')
    {
        if(i<n)
        {
            ch[i++] = tmp_str;
        }
    }
    ch[i] = '\0';
    printf("message is:%s\n", ch);
    return i;
}

13.9. 字符串处理函数

C 语言字符串库 string.h 的几个常见函数:

13.10. 字符串惯用法

size_t strlen(const char * str) {
    const char *cp =  str;
    while (*cp++ )
         ;
    return (cp - str - 1 );
}
char* strcat ( char * dst , const char * src )
{
    char * cp = dst;
    while( *cp )
        cp++; /* find end of dst */
    while( *cp++ = *src++ ) ; /* Copy src to end of dst */
        return( dst ); /* return dst */
}

13.11. 存储字符串数组的两种方式

char planets[][8] = {"Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto"};

这种方式浪费空间,如下所示:

char *planets[] = {"Mercury","Venus","Earth","Mars","Jupiter","Saturn","Uranus","Neptune","Pluto"};

推荐这种方式,如下所示:

13.12. read_line 检测读入字符是否失败

int read_line(char ch[], int n)
{
    char tmp_str;
    int i = 0;

    while((tmp_str = getchar()) != '\n' && ch != EOF)
    {
        if(i<n)
        {
            ch[i++] = tmp_str;
        }
    }
    ch[i] = '\0';
    printf("message is:%s\n", ch);
    return i;
}

第14章 预处理器

14.1. # 运算符

宏定义中使用 # 运算符可以将宏的参数(调用宏时传递过来的实参)转化为字符串字面量,如下所示:

#define PRINT_INT(x) printf(#x " = %d\n", x)

宏展开后的结果是:

printf("x" " = %d\n", x)  等价于 printf("x = %d\n", x) 

14.2. ## 运算符

宏定义中使用 ## 运算符可以将两个记号粘在一起,成为一个记号。如果其中有宏参数,则会在形式参数被实际参数替换后进行粘合。如下所示:

#define MK_ID(n) i##n
int MK_ID(1), MK_ID(2), MK_ID(3);

上述宏展开后结果为:

int i1, i2, i3;

14.3. 宏定义的作用范围

一个宏定义的作用范围通常到出现这个宏的文件末尾。

14.4. 宏定义加圆括号

14.5. 预定义宏

宏名字 宏类型 宏作用
__LINE__ 当前行的行数 整型常量
__FILE__ 当前源文件的名字 字符串字面量
__DATE__ 编译的日期(Mmm dd yyyy) 字符串字面量
__TIME__ 编译的时间(hh:mm:ss) 字符串字面量
__STDC__ 如果编译器接收标准 C,那么值为 1 整型常量

第15章 编写大规模程序

15.1. 共享宏定义和类型定义

15.2. 共享函数原型

15.3. 共享变量声明

在共享变量共享之前,不需要区分其定义和声明。

15.4. 保护头文件

为了防止头文件多次包含,将用 #ifndef 和 #endif 两个指令把文件的内容闭合起来。例如,如下的方式保护 boolean.h:

#ifndef BOOLEAN_H
#define BOOLEAN

#define TRUE 1
#define FALSE 0
typedef int Bool;

#endif

第16章 结构、联合和枚举

16.1. 结构使用 = 运算符复制

数组不能使用 = 运算符复制,结构可以使用 = 运算符复制:

struct { int a[10]; } a1, a2;
a1 = a2; /* legal, since a1 and a2 are structures */

16.2. 表示结构的类型

C 语言提供了两种命名结构的方法:

16.3. 联合的两个应用

16.4. 为联合添加 "标记字段"

为了判断联合中成员的类型,可以把联合嵌入一个结构中,且此结构还含有另一个成员 "标记字段",用来提示当前存储在联合中的内容的类型,如下所示:

#define INT_KIND 0
#define FLOAT_KIND 1

typedef struct {
  int kind; /* tag field */
  union {
    int i;
    float f;
  } u;
} Number;

void print_number (Number n){
  if (n.kind == INT_KIND){
    printf("%d", n.u.i);
  } else {
    printf("%g", n.u.f);
  }
}

16.5. 枚举

在一些程序中,我们可能需要变量只具有少量有意义的值,例如布尔类型应该只有两种可能,真值或假值。
C 语言为少量可能值的变量设计了枚举类型。


第17章 指针的高级应用

17.1. 内存分配函数

17.2. 空指针

当调用内存分配函数时,无法定位满足我们需要的足够大的内存块时,函数会返回空指针

对指针的处理惯用法如下:

p = malloc(1000);
if (p == NULL){
  /* allocation failed; take appropriate action  */
} /* 惯用法一 */

/***************************************************/

if ((p = malloc(1000)) == NULL){
  /* allocation failed; take appropriate action  */
} /* 惯用法二 */

/***************************************************/

p = malloc(1000);
if (!p){
  /* allocation failed; take appropriate action  */
} /* 惯用法三,C 语言中非空指针都为真,只有空指针为假 */

17.3. 使用 malloc 动态分配字符串

malloc 函数原型如下:

void *malloc ( size_t size );

返回指向 "新" 字符串的指针的函数,没有改变原来的两个字符串:

char *concat( const char *s1, const char *s2 ){
  char *result;
  result = malloc(strlen(s1) + strlen(s2) + 1);
  if (result == NULL){
    printf("Error: malloc failed in concat\n");
    exit(EXIT_FAILURE);
  }
  strcpy(result, s1);
  strcat(result, s2);
  return result;
}

17.4. 使用 malloc 为数组分配内存空间

需要使用 sizeof 运算符来计算每个元素所需要的空间大小:

int *a;
a = malloc( n * sizeof(int) );

一旦 a 指向动态的内存块,就可以把 a 当作数组的名字。

17.5. 使用 calloc 为数组分配内存

calloc 函数原型如下:

void *calloc ( size_t nmemb, size_t size );

通过调用以 1 为第一个实际参数的 calloc 函数,可以为任何类型的数据项分配空间。

struct point {
  int x;
  int y;
} *p;
p = calloc(1, sizeof(struct point)); /* p 执行结构,且此结构的成员 x,y 都会被设为 0 */

17.6. 使用 realloc 函数调整先前分配的内存块

一旦为数组分配完内存,后面 realloc 函数可以调整数组的大小使它更适合需要。
realloc 的原型如下:

void *realloc (void *ptr, size_t size);

17.7. 释放内存 free 函数

free 函数原型如下:

void free(void* ptr);

使用 free 函数,只要把指向不再需要内存块的指针传递给 free 函数即可,如下所示:

p = malloc (...);
q = malloc (...);'
free (p);
p = q;

悬空指针(dangling point)的问题:


第18章 声明

18.1. 声明的语法

在大多数通过格式中,声明格式如下:

[ 声明的格式 ] 声明说明符 声明符;

声明说明符(declaration specifier)描述声明的数据项的性质。声明符(declarator)给出了数据项的名字,并可以提供关于数据项的额外信息。

18.2. 变量的存储类型

C 程序中每个变量都具有 3 个性质:

变量的默认存储期限、作用域和链接都依赖于变量声明的位置:

对许多变量而言,默认的存储期限、作用域和链接是可以符合要求的。当这些性质无法满足要求时,可以通过指定明确的存储类型来改变变量的特性:auto、static、extern 和 register

18.3. 函数的存储类型

函数的声明(和定义)可以包含存储类型,但是选项只有 extern 和 static。

四种类型中最重要的就是 extern 和 static 了,auto 没有任何效果,而现代编译器已经使得 register 变得废弃无用了。

18.4. 类型限定符 const

18.5. 解释复杂声明

两条简单的规则可以用来理解任何的声明:

18.6. 初始化式


第20章 低级程序设计

20.1. 结构中的位域

C 语言可以声明结构中其成员表示位域的结构。

控制位域存储的技巧:

20.2. volatile 类型限定符

使用 volatile 类型限定符,我们可以通知编译器程序使用了内存空间 "易变" 的数据(例如从键盘缓冲区读取的数据)。


第21章 标准库

21.1 标准库概述

以下是标准库中的 15 个头。


第22章 输入 / 输出

22.1. 标准流

22.2. fopen 函数打开文件的模式

22.3. 从命令行打开文件

#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *fp;
    if (argc != 2)
    {
        printf("usage: canopen filename\n");
        return 2;
    }

    if ((fp = fopen(argv[1], "r")) == NULL)
    {
        printf("%s can't be opened\n", argv[1]);
        return 1;
    }

    printf("%s can be opened\n", argv[1]);
    fclose(fp);
    return 0;
}

22.4. ...printf 类函数

22.5. ...scanf 类函数

22.6. 复制文件

/* Copies a file */

#include <stdlib.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    FILE *source_fp, *dest_fp;
    int ch;

    if (argc != 3)
    {
        fprintf(stderr, "usage: fcopy source dest\n");
        exit(EXIT_FAILURE);
    }

    if ((source_fp = fopen(argv[1], "rb")) == NULL)
    {
        fprintf(stderr, "Can't open %s\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    if ((dest_fp = fopen(argv[2], "wb")) == NULL)
    {
        fprintf(stderr, "Can't open %s\n", argv[2]);
        fclose(source_fp);
        exit(EXIT_FAILURE);
    }

    while ((ch = getc(source_fp)) != EOF)
    {
        putc(ch, dest_fp);
    }

    fclose(source_fp);
    fclose(dest_fp);

    return 0;
}

参考

上一篇 下一篇

猜你喜欢

热点阅读