`

【转】《C专家编程》读书笔记(1)

阅读更多

原文地址:http://blog.csdn.net/arthurkingios/archive/2007/05/07/1599833.aspx

 

***C的发展历程***

BCPL -> B -> New B -> 早期的C

注释:
BCPL(Basic Combined Programming Language) 基本组合编程语言
B 在BCPL的基础上进行了简化,但由于硬件系统的内存限制,它只允许放置解释器,而不是编译器。它同时保持了BCPL语言无类型的特点,仅有的操作数就是机器的字。
New B 能同时解决多种数据类型,采用了编译模式而不是解释模式,并引入了类型系统。
早期的C 有以下一些语言特性:(1)数组下标从0而不是1开始;(2)基本数据类型直接与底层硬件相对应(这一点带来了极高的效率和移植性);(3)数组名可看作指针;(4)不允许嵌套函数。

C编译器未实现的功能在运行时进行处理,既可出现在应用程序代码中,也可出现在运行时函数库中。

***ANSI C***

ANSI C标准的几个术语:
(1)不可移植的代码:
         (a)由编译器定义的:由编译器设计者决定采取何种行动,可能并不相同,但都是正确的。
                   例如:当整型数向右移位时,要不要扩展符号位
         (b)未确定的:在某些正确情况下的做法,标准并未明确规定应该怎样做。
                   例如:计算参数的顺序
(2)坏代码:
        (a)未定义的:在某些不正确情况下的做法,但标准并未规定应该怎么做,即你可采取任何行动。
                  例如:当一个有符号整数溢出时该采取什么行动
        (b)约束条件:必须遵守的限制或要求,若不遵守,则程序行为将变成未定义的。
                  例如:%操作符的操作数必须属于整型(属于约束条件),若不符合,必引发一条错误信息。
                  声明一个已有的malloc函数(不属于约束条件),则可能出现任何情况。

K&R C与ANSI C的一个重大区别:原型
原型的目的是当我们对函数作前向声明时,在形参类型中增加一些信息。这样,编译器就能够在编译时对函数调用中的实参和函数声明中的形参之间进行一致性检查。

                             使用原型之前                    使用原型之后
函数声明:         
char* strcpy();                      char* strcpy(char* dst, char* src)
函数定义:         
char* strcpy(dst, src)          char* strcpy(char* dst, char* src)
                         
char* dst, char* src;             { ... }
                          
{ ... }

***指针赋值的规范***

规范:两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须具有右边指针所指向类型的全部限定符。

下面是相关示例:

(1)合法情况:

char* cp;
const char* ccp;
ccp 
= cp;

左操作数是一个指向有const限定符的char的指针,右操作数是一个指向无限定符的char的指针,char类型和char类型是相容的,左操作数所指向的类型具有右操作数所指向的类型的限定符(无),再加上自身的限定符(const),因而此赋值是合法的。

(2)非法情况:

char** cp;
const char** ccp;
ccp 
= cp;

左操作数是一个指向无限定符的char类型的指针的指针,右操作数是一个指向有const限定符的char类型的指针的指针,虽然其中cp指向的和ccp指向的都是无限定符的指针,且二者指向的指针是相容的(原因参见情况1),但由于相容性是不能传递的,因而此赋值是非法的。

***对无符号类型的建议***

尽量不要在代码中使用无符号类型,只有在使用位段和二进制掩码时,才可以用无符号数,但也要注意在表达式中使操作数均为无符号数或有符号数,这样就不必由编译器来选择结果的类型。

下面是一个错误示例:

#include "iostream"

using namespace std;

double array[] = 23,34,12,17,204,99,16 };
#define TOTAL_ELEMENTS (sizeof(array)/sizeof(array[0]))

int main()
{
    
int d = -1;
    
double x;

    
if(d <= TOTAL_ELEMENTS - 2)
    
{
        x 
= array[d+1];
     }


    
return 0;
}

TOTAL_ELEMENTS的类型是unsigned int(因为sizeof()的返回类型是无符号数),if语句在int和unsigned int之间测试相等性,所以d被升级为无符号数,-1转化为unsigned int的结果是一个非常巨大的正整数,致使表达式的结果为假(与预期不符)。要修正这个问题,只需对TOTAL_ELEMENTS进行强制类型转换既可:

if(d <= (int)TOTAL_ELEMENTS - 2)

***C语言的缺陷***

C语言的缺陷:

(1)多做之过:
         (a)由于存在fall through(默认在没有break时会顺序执行case),switch语句会带来麻烦;
         (b)将相邻的字符串常量自动合并成一个字符串;
         (c)太多的缺省可见性:在缺省情况下,函数的名字是全局可见的,如果要限制对这个函数的访问,就必须加个static关键字(在这个文件之外不可见)。范围过宽的问题常见于库中,一个库要让一个对象在另一个库中可见,唯一的办法是让它变得全局可见。但这样一来,它对于链接到该库的所有对象都是可见的了。

(2)误做之过:
         (a)符号重载带来的歧义;
         (b)有些运算符的优先级不符合常规理解;

优先级问题             表达式           常规理解                           实际结果
.的优先级高于
*   *p.f                 p所指对象的字段             对p取f偏移,作为指针再解引用
                                                     (
*p).f                                  *(p.f)
[]高于
*                  int* ap[]         ap是一个指向int数组      ap是一个元素为int指针的数组
                                                     的指针                               
int* (ap[])
                                                     
int (*ap)[]
函数()高于
*              int* fp()         p是一个函数指针,      fp是个函数,返回int*
                                                       所指函数返回int                
int* (fp())
                                                       
int (*fp)()
==!=高于赋值符     c=getchar()    (c=getchar())!=EOF       c=(getchar()!=EOF)
                                 
!=EOF
逗号在所有运算符 i 
= 1,2            i = (1,2)                           (i = 1),2
中优先级最低

          (c)例如gets()等文件I/O函数的缓冲区溢出问题;

(3)少做之过:
         (a)标准参数处理未区分选项和其它参数;
         (b)注释歧义;
         (c)自动变量引发的问题:在C语言中,自动变量在堆栈中分配内存,当包含自动变量的代码块或函数返回时,它们所占用的内存即被收回。
          最好的解决方案就是要求调用者分配内存来保护函数的返回值。为了提高安全性,调用者应该同时指定缓冲区的大小:

void func(char* result, int size)
{
     ...
    strncpy(result,
"That's right",size);
}

          
//调用过程
buffer = malloc(size);
func(buffer,size);
free(buffer);

***C语言中的声明***

(1)如果想要把什么东西的类型强制转换为指向数组的指针,就不得不使用下面的语句来表示这个强制类型转 换:

char (*j)[20];    //j是一个指向数组的指针,数组内有20个char元素
= (char (*)[20])malloc(20);

(2)涉及指针和const的声明可能会出现几种不同的顺序:

const int* grape;        //指针所指向的对象是只读的
int const* grape;        //同上
int* const grape;        //指针是只读的
const int* const grape;  //指针和指针所指向的对象都是只读的

(3)几种合法的声明形式:

          函数的返回值允许是一个函数指针,如:int (*fun())();
          函数的返回值允许是一个指向数组的指针,如:int (*fun())[];
          数组里面允许有函数指针,如int (*foo[])();

(4)最好不要将struct的声明和定义混合在一起:

//不好的做法
struct veg int weight,price; } onion, radish, turnip;
//建议的做法
struct veg int weight,price; };    //声明
struct veg onion, radish, turnip;    //定义

(5)联合又被称作变体记录,它的外表与结构相似,但在内存布局上存在关键性的区别。在结构中,每个成员依次存储,而在联合中,所有的成员都从偏移地址零开始存储,在某一时刻,只有一个成员真正存储于该地址;

联合通常用来节省空间,此外联合也可以把同一个数据解释成两种不同的东西,例如:

union bits32_tag
{
    
int whole;                                           //1个32位的值
    struct char c0, c1, c2, c3; } byte;    //4个8位的字节
}

这个联合允许程序员提取整个32位值(value.whole),也可以提取单独的字节字段如value.byte.c0等。

(6)枚举与#define作用相似,但具有一个优点:#define定义的名字一般在编译时被丢弃,而枚举名字一直可见;

enum size { small = 7, medium, large = 10 };

(7)C语言声明的优先级规则:
     (a)声明从它的名字开始读取,然后按照优先级顺序依次读取;
     (b)优先级从高到低依次是:
              (1)声明中被括号括起来的那部分;
              (2)后缀操作符(()表示这是一个函数,[]表示这是一个数组);
              (3)前缀操作符(*表示指向...的指针);
     (c)如果const或volatile关键字后面紧跟类型说明符,则它作用于该类型说明符;否则它作用于其左边紧邻的指针星号。

根据上述规则,可分析以下声明:

char* const *(*next)();

解释:next是一个指针,它指向一个函数,该函数返回另一个指针,该指针指向一个类型为char的常量指针。

(8)typedef和宏文本替换之间的存在一个关键性的区别,即typedef是一种彻底的“封装”类型:

         (a)可以用其它类型说明符对宏类型名进行扩展,而typedef禁止这样做;

#define peach int
unsigned peach i;    
//没问题
typedef int banana;
unsigned banana i;   
//非法

         (b)在连续几个变量的声明中,typedef能保证所有变量均为同一种类型;

#define int_ptr int*;
int_ptr chalk, cheese;    
//经扩展后,变为int* chalk, cheese;
typedef int* int_ptr;
int_ptr chalk, cheese;    
//chalk、cheese均是int*类型

(9)不要为了方便起见对结构使用typedef,这样做好处很小,且容易混淆;

 

分享到:
评论

相关推荐

    C专家编程读书笔记(上).pdf

    C专家编程读书笔记(上).pdf

    C 专家编程 适合有一定C基础的人看

    《C专家编程》是一本面向有一定C语言基础的读者的书籍,它的目标是帮助这些读者在C编程方面达到专家级别的水准。这本书由彼得·范·德·林登(Peter Van Der Linden)撰写,由徐波谋翻译,首次出版于2002年12月,由...

    谭浩强老师C语言程序设计PPT及笔记

    “谭浩强C程序设计和读书笔记以及PPT”这部分可能是谭浩强老师的个人授课笔记,其中可能包含了他对C语言更深层次的见解和独特的解题方法,也可能包括了课堂讲解的PPT,这些都为读者提供了丰富的学习材料。阅读这些...

    尚观培训linux许巍老师关于c语言的课程笔记和讲义

    【尚观培训Linux许巍老师关于C语言的课程笔记与讲义】是针对想学习或深化C语言理解的IT从业者及爱好者的一份宝贵资源。尚观是一家知名的IT培训机构,其提供的课程涵盖了广泛的IT领域,包括嵌入式Linux驱动开发。这份...

    《架构整洁之道》读书笔记.pdf

    《架构整洁之道》是软件开发领域的一本经典著作,由知名专家Robert C. Martin撰写。Martin先生不仅是cleancoders.com的联合创始人,还是Uncle Bob Consulting LLC的创始人,他在全球范围内提供软件开发咨询服务,...

    转嵌入式 Linux入门笔记

    这份"转嵌入式Linux入门笔记"提供了宝贵的资源,帮助初学者快速踏入这个领域。下面,我们将详细探讨嵌入式Linux的基本概念、重要性以及学习路径。 嵌入式Linux是指将Linux操作系统与硬件平台紧密结合,用于实现特定...

    马士兵多线程训练营笔记

    在IT领域,多线程是并发编程中的一个关键概念,特别是在Java这样的多线程支持语言中。马士兵是一位知名的IT教育专家,他的多线程训练...在阅读和学习这些笔记时,结合实际的编程练习将有助于更好地理解和巩固所学知识。

    python核心笔记pdf

    Python是一种高级、通用的编程语言,以其简洁明了的语法和强大的功能而受到广大程序员的...通过阅读这份笔记,读者不仅可以掌握Python的基本语法,还能了解到更高级的概念和技术,为成为Python开发专家打下坚实的基础。

    韦东山二期驱动笔记

    【韦东山二期驱动笔记】是一份针对Linux驱动程序开发的参考资料,由知名Linux技术专家韦东山编撰。这份笔记深入浅出地讲解了Linux内核驱动开发的基础与实践,旨在帮助读者理解和掌握Linux系统下的设备驱动编写技巧。...

    韩顺平笔记 PHP笔记 资料难得 赶紧下载吧

    韩顺平老师的笔记中可能涵盖了这些基础知识的实例和讲解,通过阅读和实践,可以逐步提升PHP编程能力。此外,资料中的“3C3N导航-浓缩精华-双重搜索引擎.url”可能是提供其他学习资源的链接,帮助你在学习过程中找到...

    C语言(谭浩强word版)和同步训练

    C语言是一种广泛应用于系统编程、应用编程、嵌入式开发等多个领域的高级编程语言,由Dennis Ritchie在20世纪70年代初为UNIX操作系统开发。它的语法简洁、灵活,且对硬件的控制能力强,因此深受程序员喜爱。谭浩强...

    ARM嵌入式系统技术笔记-基于LPC2300上

    在软件开发方面,读者可以学习到C语言编程技巧,特别是在嵌入式环境下的优化方法。同时,了解如何使用开发工具,如Keil uVision或者GCC编译器,进行项目配置、编译、链接以及调试。这有助于读者将理论知识转化为实践...

    精读特训笔记.zip

    本压缩包“精读特训笔记.zip”包含了关于精读学习方法和技巧的详细资料,旨在帮助用户提升阅读理解能力,尤其是对于专业文章和技术文档的理解。精读是一种深入理解文本的阅读策略,它要求读者不仅理解文字表面意思,...

    开发专家 Visual C 开发入行真功夫光盘

    标题中的“开发专家 Visual C 开发入行真功夫光盘”表明这是一份与Visual C++编程相关的学习资源,可能是包含教程、实例代码、练习题等的光盘内容。Visual C++是微软公司开发的一款集成开发环境(IDE),主要用于...

    C语言学习课件(北邮版)

    这个课件可能包含了一系列的讲座笔记、课后练习、实例代码和解题思路,由北京邮电大学的专家精心编撰。北京邮电大学作为中国知名的高等学府,其计算机科学教育具有较高的权威性,因此,这个课件的质量可以得到保证。...

    一步一步学VB2005数据库编程

    "WROX-Expert_One_On_One_Visual_Basic_2005_Database_Programmingc.pdf"看起来是一本完整的电子书,由著名的技术出版商Wrox出版,专门探讨VB2005的数据库编程,包含专家级别的指导和实践案例。"E书说明.txt"可能是...

    FPGA学习笔记 FPGA

    FPGA(Field-Programmable Gate Array)是一种可编程逻辑器件,允许用户根据需求自定义数字电路。在本文中,我们将探讨FPGA的学习路径、主要工具以及相关资源,旨在为初学者提供指导。 首先,学习FPGA需要掌握硬件...

    STM32自学笔记 2012.rar

    STM32是一款基于ARM ...通过阅读《STM32自学笔记》,读者将能全面了解STM32的基本概念、开发流程和实战技巧,为成为STM32领域的专家奠定坚实基础。同时,结合实际项目练习,能够更好地巩固理论知识,提高动手能力。

    oracle笔记 (PL-SQL)

    Oracle笔记(PL-SQL)是关于数据库管理和编程的一个重要主题,主要聚焦于Oracle数据库系统中的结构化查询语言(SQL)的扩展——PL/SQL。PL/SQL是一种过程化语言,结合了SQL的功能,使得开发者能够编写复杂的数据库应用...

Global site tag (gtag.js) - Google Analytics