浓缩版 《C和指针》基础篇(Chpt.1~Chpt.9)

导语

近日,笔者在课业之余阅读了《C和指针(Pointers on C)》 (by Kenneth A.Reek)一书,从中记录了关于C语言的诸多知识点,包括在C语言基础特性的学习过程中没有涉及到的基本方面,以及与指针相关的进阶话题。

由于初次记录过程中有诸多不详细之处,故将其中内容重新整理如下,方便今后查看,也希望能给需要这方面知识但没有足够时间阅读原书的朋友提供一点帮助。(当然,文中只记录了初次学习C语言特性过程中没有过多涉及的内容。基本的语法、数据类型等知识不再进行记录。)

这一部分,是书中内容的前九章,相对较为基础,是C语言基本特性的轻度延伸,故合为一篇,记为基础篇。

 

目录
Chpt.1 快速上手(讲述C语言程序的编译等基本内容,本文不作记录)

Chpt.2 基本概念

Chpt.3 数据

Chpt.4 语句 (讲解C语言中的基本语句,包括if, for, while等,本文不作记录)

Chpt.5 操作符和表达式

Chpt.6 指针

Chpt.7 函数

Chpt.8 数组

Chpt.9 字符串、字符和字节

下面,进入正式的内容。

 

Chpt.2 基本概念
  0. 关于编程风格
  (0)if, while等语句以及函数名之后的括号从属于之前的内容(即if, while等),而不属于括号中的内容,故应在括号中内容两端留一个空格。如:

1 if ( a != 3 )
2 {
3     // ....
4 }
5 while ( 1 )
6 {
7     // ....
8 }
9 strcmp( dst, src );

  (1)使用[Tab]而不是空格做好缩进
  (2)绝大多数注释应该成块出现,从而在视觉上更突出。
  (3)操作符使用时大多隔以空格,但操作符太多时可以省略部分空格以突出层次。
  (4)不同人的代码风格可能略有差别,但尽量始终使用同一种较好的风格。

  1. 关于注释

    块注释(即/* ... */)不能嵌套使用。因为注释会在第一个/*处结束,导致两个*/之间的内容(包括第二个*/)不再被认为是注释。

  2. 关于字符

   (0)三字母词:标准C中定义了一些三字母词(trigrph),即三个字符的序列用以表示另一个字符。具体如下:
      ??( : [
      ??) : ]
      ??! : |
      ??< : {
      ??> : }
      ??' : ^
      ??= : #
      ??/ : \
      ??- : ~

      也就是说,应当尽量避免两个问号连续出现的情形。出现时,可用“\?”表示“?”
   (1)字符串中引号的表示:在字符串中应使用\"表示双引号。
   (2)可用\ddd表示八进制下的ddd作为ASCII码所对应的字符,同理\xddd表示十六进制的情况。


Chpt.3 数据

  0. 指针变量的声明:

    在下面这条语句中,符号‘*’从属于后面的变量名ptr,而非前面的类型名int。因此这条语句的作用是声明了整型指针ptr和整型变量var。

     int *ptr, var; 

  1. 用typedef简化指针的声明:

    我们可以书写这样一段代码:

     typedef char* pchar; 

    从而我们可以用 pchar pc; 这样的语句声明字符型指针pc。

    但通常,我们只在需要用到多级指针的时候,采取这种操作来减少*的个数,提高可读性。否则,不必使用这种操作。

  2. 常量指针与指针常量的区别

    看下面几种声明:

int  const        pi;          //声明整型常量pi
int  const       *pci;         //声明指向整型常量的指针pci
int *const        cpi;         //声明指向整型的常量指针cpi
int  const *const cpci;        //声明指向整型常量的常量指针cpci

    只需时刻记住,'*'运算符将与其后的表达式结合,作为整体。

    例如第二行的声明,表示*pci是一个整型常量,也就是说,pci是一个指向整型常量的指针。

    再如第三行的声明,const cpi表明cpi是一个常量,而*const cpi是一个整型变量,所以cpi是一个指向整型的常量指针。

    另外,int const 和 const int的作用相同,但新式的语言风格中常用前者。

  3. 变量的几个基本属性:

    (0)作用域

      变量按作用域可分为:

      a. 代码块作用域:声明在一组{}之中,只在这组{}形成的代码块内起作用的变量。

      b. 文件作用域:声明在{}之外,在整个文件中起作用,并可写在头文件中通过#include在其他文件中引用的变量。

      c. 原型作用域:在函数原型的参数列表中声明的变量,它们只在所在的原型声明语句中起作用(函数定义时参数名可与原型声明中不相同)。

      d. 函数作用域:与goto语句相关,作者希望我们永远不要用到这个知识。

    (1)链接属性

      变量按链接属性可分为:

      a. 外部(external):不论在几个源文件中声明都属于同一个实体。

      b. 内部(internal):在同一个源文件中的所有声明属于同一个实体,在不同源文件中的声明属于不同实体。

      c. 无(none):不同的声明即为不同的实体。

      链接属性的确定:

      a. 声明时若不加相关修饰词,全局变量和函数名默认为external,其他默认为none。

      b. 在默认为external(即全局变量和函数名)的声明前加static,则为internal。

      c. 在默认不为external的声明前加extern,则为external。

      d. 第二次声明变量时不论是否添加关键词staticextern,都不改变初次声明时的链接属性。

    (2)存储类型

      变量有以下三种存储位置:

      a. 普通内存:代码块之外的变量,即静态变量。代码运行过程中始终存在。默认会初始化为0。

      b. 运行时堆栈:代码块内部声明的变量。程序离开该代码块时将被销毁。可添加static关键词使其变为静态。不会默认初始化为有效的值。

      c. 寄存器:添加register关键词进行声明。读写速率高,但寄存器数目有限。超出上限时register关键词将被忽略。

    小结

      注意static用于不同位置的含义并不相同。

      用于全局变量、函数名前时,是作用于其链接属性,将其设置为internal。

      用于代码块中的变量时,是作用于其存储类型,将其设置为静态存储。

 

Chpt.5 操作符和表达式

  这一部分,主要讲解了所有操作符的优先级、结合性。不作赘述,见下表。(来自原书)

           

            

 

Chpt.6 指针

  字符常量和字符变量的类型转换

    当需要将一个字符常量作为char *类型的函数的返回值时,可将其强制转换为char *类型。如以下代码:

char *find_char(char const *source, char const *chars)
{
    if (source == NULL || chars == NULL)
        return NULL;
    for (; *source; source++)
    {
        if (source && *source == *chars)
        {
            return (char *)source;     /* 将char const *类型的source强制转化为char *类型并返回 */
        }
    }
    return NULL;
}

 

Chpt.7 函数

  0. 数组作为函数参数:

    在函数的参数列表中写type *para和type para[]的效果完全相同,实际生成的变量para都是指针类型。

    因此,无法真正将数组作为参数传入函数。也就是说,当需要用到传入数组的长度时,无法通过sizeof等操作符获取,只能作为另一个参数传入。如:

     void sort( int *array, int n_array ); 

   1. 可变参数列表:

    在头文件stdarg.h中,提供了一组函数和类型,用来为自定义的函数添加可变的参数列表。具体的用法如下:

    a. 引用头文件stdarg.h。

    b. 在需要使用可变参数的函数参数列表中书写"..."(不包括引号)。

    c. 在函数中声明一个类型为va_list的变量。

    d. 调用 va_start(va_list var_arg, int n_values) 函数准备开始访问可变参数。

    e. 调用 va_arg(va_list var_arg, type) 函数引用当前指向的可变参数值并使其指向下一个可变参数。

    f. 调用 va_end(va_list var_arg) 结束对可变参数列表的引用。

    下面给出一个寻找任意长度的整数列中最大值的例子:

int max_list(int n, ...)         /* 在参数列表中使用...声明可变参数列表 */
{
    int ret;
    int temp;
    va_list vl;                  /* 声明可变参数列表变量vl */
    va_start(vl, n);             /* 开始读取可变参数 */
    ret = va_arg(vl, int);
    for (int i = 1; i < n; i++)
    {
        if ((temp = va_arg(vl, int)) > ret)
            ret = temp;
    }
    va_end(vl);
    return ret;
}

    调用方法: max_list( 6, 1, 3, 5, 7, 9, 4 );    /* 返回值为9 */ 

 

Chpt.8 数组

  0. 字符数组和字符串

    注意以下两种声明及初始化的区别:

     char string[] = "Hello"; 

     char *string = "Hello"; 

    第一条语句声明了一个字符型数组,用"Hello"进行初始化,此时"Hello"相当于{'H', 'e', 'l', 'l', 'o'},故string被确定为一个长度为5的字符数组。

    第二条语句声明了一个字符型指针,用"Hello"进行初始化,这里的"Hello"即字符串常量"Hello",故string是一个字符串,值为"Hello"。

  1. 多维数组

    多维数组在声明时,只有第一维的长度可以缺省,其他维必须在声明时指定长度。(否则,编译器无法正确判断在何时“换行”)

    尤其注意,多维数组作为函数参数时,参数列表中可以写 matrix[][10] 或 (*matrix)[10] ,但不能写作 **matrix ,理由同上。

 

Chpt.9 字符串

  0. 字符串操作函数

    以下函数均来自<string.h>。

    (0) char *strchr(char const *str, int ch); 

      返回指向str中第一个字符ch的指针,若没有则返回NULL。

    (1) char *strpbrk(char const *str, char const *group); 

      返回指向str中第一个包含于group中的字符的指针,若没有则返回NULL。

    (2) char *strstr(char const *s1, char const *s2); 

      返回指向s1中第一次出现完整的s2的位置,若没有则返回NULL。

    (3) size_t strspn(char const *str, char const *group); 

      返回str中从起始位置开始,匹配group中的字符的长度。

      例如:表达式 strspn("32145164234", "123"); 的值为3,因为前3个字符匹配"123"中的字符,而第4个字符不匹配"123"中的字符。

    (4) size_t strcspn(char const *str, char const *group); 

      与strspn完全类似,但返回的是不匹配group中字符的长度。

    (5) char *strtok(char *str, char const *sep); 

      str是待处理的字符串,sep是分隔符集合。若str不为NULL,函数将找到str中的第一个分隔符,返回这个分隔符前的子串,并保存当前位置

      若传入的str为NULL,则以之前保存的位置为起点,找到后面的第一个分隔符,做同样的操作。

      (说人话,就是第一次的str传入要处理的字符串,之后循环传入NULL进行处理,直到返回值为NULL,则表明原字符串已处理完毕)

      由于原字符串可能会被破坏,所以建议用strcpy创建一份复制进行处理。

  1. 字符操作函数

    以下函数均来自<ctype.h>

    (0)字符分类函数:

int iscntrl( char ch );     //控制字符
int isspace( char ch );     //空白字符:' ', '\f', '\n', \r', \t', '\v'
int isdigit( char ch );
int isxdigit( char ch );
int islower( char ch );
int isupper( char ch );
int isalpha( char ch );
int isalnum( char ch );
int ispunct( char ch );     //任何不属于数字或字母的图形字符
int isgraph( char ch );
int isprint( char ch );     //可打印字符,包括空白字符和图形字符

    (1)字符转换函数:

char tolower( char ch );
char toupper( char ch );

  2. 数组操作函数

    下列函数均来自<string.h>

void *memcpy(void *dst, void const *src, size_t length);
void *memmove(void *dst, void const *src, size_t length);
void *memcmp(void const *a, void const *b, size_t length);
void *memchr(void const *a, int ch, size_t length);
void *memset(void *a, int ch, size_t length);   // 将a的前length个元素初始化为ch

    注意:length应使用 sizeof( type ) * ele_num 传入

 

总结

  以上就是《C和指针》一到九章中的重要知识点。在进阶篇中,将继续总结本书后续章节中的知识点。

posted @ 2019-04-19 21:32  DrChuan  阅读(276)  评论(2编辑  收藏  举报