《高质量程序设计指南C/C++语言》笔记总结

在对林锐,韩永泉编著的《高质量程序设计指南C/C++语言》的学习中,我从中了解到了很多编程的小细节和重要的概念,特总结规整如下:

1.标准C语言允许任何非void类型的指针和void类型的指针之间进行直接的相互转换。但在C++中,可以把任何类型的指针直接指派给void类型指针,因为void*是一种通用指针;但是不能反过来将void类型指针直接指派给任何非void类型的指针,除非进行强制转换。因此在C语言环境中我们就可以先把一种具体类型的指针如int*转换为void*类型,然后再把void*类型转换为double*类型,而编译器不会认为这是错误的。然而这种做法确实存在着不易察觉的安全问题(内存扩张和截断等),这是标准C语言的一个缺陷。

2.如果计数器从0开始计数,则建议for语句的循环控制变量的取值采用“前闭后开区间”写法,要防止出现“差1”错误。

3.对于多维数组来说,正确的遍历方法要看语言以什么顺序来安排数组元素的存储空间。比如FORTRAN是以“先列后行”的顺序在内存中连续存放数组元素,而C/C++则是以“先行后列”的顺序来连续存储数组元素。因此,遍历方式与存储方式相同。根据事实证明:“先列后行”遍历发生的页面交换次数要比“先行后列”多,且cache命中率相对也低。这恰恰就是导致“先列后行”遍历效率降低的原因。

4.常用常量可分为:字面常量、符号常量、契约性常量、布尔常量和枚举常量等。

(1).字面常量:例如直接出现的各种进制的数字、字符(‘’括住的单个字符)或字符串(“”括住的一系列字符)等。其只能引用不能修改,所以语言实现一般将它保存在程序的符号表里而不是一般的数据区中。符号表是“只读”的,其实它是一种访问保护的机制,千万不要理解为只读存储器(ROM)。除了字符串外,你无法取一个字面常量的地址;

(2).符号常量:存在两种符号常量(用#define定义的宏常量和用const定义的常量),宏常量在本质上就是字面常量。在C语言中,用const定义的常量其实是值不能修改的常量,因此会给它分配存储空间(外连接的);但在C++中,const定义的常量要具体情况具体对待:对于基本数据类型的常量,,编译器会把它放在符号表中而不分配存储空间,而ADT/UDT的const对象则需要分配存储空间(大对象)。从理论上讲,只要你手中拥有一个对象的指针(内存地址),你就可以设法绕过编译器随意修改它的内容,除非该内存受到操作系统的保护。也就是说,C++并没有提供对指针有效性的静态检查,而是把它丢给了操作系统,这正是使用指针的危险所在。在标准C语言中,const符号常量默认是外连接的(分配内存),也就是说你不能两个(或两个以上)编译单元中同时定义一个同名的const符号常量(重复定义错误),或者把一个const符号常量定义放在一个头文件中而在多个编译单元中同时包含该头文件。但是在标准C++中,const符号常量默认是内连接的,因此可以定义在头文件中。当在不同的编译单元中同时包含该头文件时,编译器认为它们是不同的符号常量,因此每个编译单元独立编译时会分别为它们分配存储空间,而在连接时进行常量合并。

(3).契约性常量:契约性const对象的定义并未使用const关键字,但被看做是一个const对象,例如:

void ReadValue(const int& num)

{

cout<<num;

}

int main(void)

{

int n = 0;

ReadValue(n);//契约性const,n被看做是const

}

(4).枚举常量:C++/C的构造类型enum实际上常用来定义一些相关常量的集合,标准C++/C规定枚举常量的值是可以扩展的,并非受限于一般的整数型的范围。

5.函数调用规范(调用约定),其决定了函数调用的实参压栈、退栈及堆栈释放的方式,以及函数名改编的方案,也即命名方案。Windows环境下常用的调用规范有:

(1)._cdecl:这是C++/C函数的默认调用规范,参数从右向左依次传递并压入堆栈,由调用函数负责堆栈的清退,因此这种方式利于传递个数可变的参数给被调用函数(因为只有调用函数才知道它给被调用函数传递多少个参数及它们的类型)。

(2)._stdcall:这是Win API函数使用的调用规范。参数从右向左依次传递并压入堆栈,由被调用函数负责堆栈的清退。该规范生成的函数代码比_cdecl更小,但当函数有可变个数的参数是会转为_cdecl规范。在Windows中,宏WINAPI、CALLBACK都定义为_stdcall;

(3)._thiscall:是C++非静态成员函数的默认调用规范,不能使用个数可变的参数。当调用非静态成员函数的时候,this指针直接保存在ECX寄存器中而非压入函数堆栈。其他方面与_stdcall相同;

(4)._fastcall:该规范所修饰的函数的实参将被直接传递到CPU寄存器中而不是内存堆栈中(这就是“快速调用”的含义)。堆栈清退由被调用函数负责。该规则不能用于成员函数。

注意:类的静态成员函数的默认调用规范不是thiscall,类的友元函数的调用规范也不是thiscall,它们都是由函数本身指定或者由工程设定的。特别地,COM接口的方法都指定_stdcall调用规范,而我们自己开发COM对象及其接口时也可以指定其他调用规范。

6.如果函数的返回值是一个对象,有些场合下可以用“返回引用”替换“返回对象值”,这样可以提高效率,而且还可以支持链式表达。而有些场合下只能用“返回对象值”而不能用“返回引用”,否则会出错,例如:

char String

{

……

//赋值函数

String& operator = (const String &assign);

//相加函数,如果作为成员函数来重载,则只能有一个参数

friend String operator + (const String &lh ,const String &rh);

private:

char *m_data;

}

对于赋值函数,应当用“返回引用”的方式返回String对象(即this对象)。

String& operator = (const String &assign)

{

if(this == &assign)

{

return *this;

}

char *p = new char[strlen(assign.m_data)+1];

strcpy(p,assign.m_data);

delete m_data;

m_data = p;

return *this;//返回的是*this的引用,没有内容拷贝的过程

}

对于相加函数,应当用“返回对象值”的方式返回String对象,这将把局部对象temp及其真正的字符串值拷贝一份给调用环境接受者。如果改用“返回引用”,那么函数返回值是一个指向局部对象temp的“引用”(即地址),而temp在函数结束时被自动销毁,将导致返回的“引用”无效。

String operator + (const String &lh ,const String &rh)

{

String temp;

temp.m_data = new char[strlen(s1.m_data)+strlen(s2.m_data)+1];

strcpy(temp.m_data,s1.m_data);

strcat(temp.m_data,s2.m_data);

return temp;//执行string对象及其字符串内容的拷贝

}

7.在函数体的“入口处”和“出口处”从严把手,从而提高函数的质量。“入口处”:使用断言;“出口处”:对return语句的正确性和效率进行检查。

8.对于ADT/UDT的输入参数,应该将“值传递”改为“const&传递”,目的是提高效率(void Func(A a)改为void Func(const A &a));对于基本数据类型的输入参数就不需改变,否则即达不到提高效率也会让人费解(void Func(const int x)改为void Func(const int &x))。

9.数组实际上也是一个可以递归定义的概念:任何维数的数组都可以看作是比它少一维的数组组成的一维数组。例如int a[3][4][5]可以看做是由二维数组int b[4][5]组成的一维数组,其长度为3。

数组和指针之间存在等价关系:

(1).一维数组等价于元素的指针,例如:

int a[10]  <==> int *const a;

(2).二维数组等价于指向一维数组的指针,例如:

int b[3][4] <==> int (*const b)[4];

(3).三维数组等价于指向二维数组的指针,例如:

int c[3][4][5] <==> int (*const c)[4][5];

10.引用和指针的比较:

(1).引用在创建的同时必须初始化,即引用到一个有效的对象;而指针在定义的时候不必初始化,可以在定义后面的任何地方重新赋值;

(2).不存在NULL引用,引用必须与合法的存储单元关联;而指针则可以是NULL。注:不要用字面变量来初始化引用;

(3).引用一旦被初始化为指向一个对象,它就不能被改变为对另一对象的引用(即“从一而终、矢志不渝”);而指针在任何时候都可以改变为指向另一个对象。给引用赋值并不是改变它和原始对象的绑定关系;

(4).引用的创建和销毁并不会调用类的拷贝构造和析构函数;

(5).在语言层面,引用的用法和对象一样;在二进制层面,引用一般都是通过指针来实现的,只不过编译器帮我们完成了转换。

注:引用的主要用途是修饰函数的形参和返回值,引用既具有质真的效率,又具有变量使用的方便性和直观性。

11.条件编译

(1).#if、 #elif、 #else、 #ifdef、 #ifndef;

(2).#error编译伪指令用于输出与平台、环境等有关信息;

(3).#pragma编译伪指令用于执行语言实现所定义的动作;

(4).#和##运算符

构串操作符#只能修饰带参数的宏的形参,它将实参的字符序列(而不是实参代表的值)转换成字符串常量。例如:

#define STRING(x) #x #x #x

#define TEXT(x)    "class" #x "Info"

那么宏引用:

int abc = 100;

STRING(abc)

TEXT(abc)

展开后结果是:

“abcabcabc”

"classabcInfo"

合并操作符##将出现在其左右的的字符序列合并成一个新的标识符(注意:不是字符串)。例如:

#define CLASS_NAME(name) class##name

#define MERGE(x,y) x##y##x

则宏引用:

CLASS_NAME(SysTimer)

MERGE(me,To)

将分别扩展为如下两个标识符:

classSysTimer

meTome

使用合并操作符##时,产生的标示符必须预先有定义,否则编译器会报“标识符未定义”的编译错误。

12.在C++中,凡是使用static关键字声明和定义的程序元素,不论其作用域是文件、函数或是类,都将具有static存储类型,并且其生存期限为永久,即在程序开始运行时创建而在程序结束时销毁。

13.类String的构造函数、拷贝构造函数、拷贝赋值函数和析构函数

class String

{

public:

String(const char *str = "");//构造函数

String(const String& copy);//拷贝构造函数

String& operator = (const String& assign);//拷贝赋值函数

~String();//析构函数

private:

size_t m_size;//保存当前长度

char   *m_data;//指向字符串的指针

}

String::String(const char *str = "")//构造函数

{

if(str == NULL)

{

m_data = new char[1];

*m_data = ‘\0‘;

m_size = 0;

}

else

{

size_t length = strlen(str);

m_data = new char[length+1];

strcpy(m_data,str);

m_size = length;

}

}

String::String(const String& copy)//拷贝构造函数

{

size_t len = strlen(copy.m_data);

m_data = new char[len+1];

strcpy(m_data,copy.m_data);

m_size = len;

}

String::String& operator = (const String& assign)//拷贝赋值函数

{

//检查自赋值

if(this != &assign)

{      //分配新的内存资源,并拷贝内容

char *temp = new char[strlen(assign.m_data)+1];

strcpy(temp,assign.m_data);

//释放原有的内存资源

delete []m_data;

m_data = temp;

m_size = strlen(assign.m_data);

}

//返回本对象引用

return *this;

}

String::~String()//析构函数

{

delete []m_data;

}

14.成员函数的重载、覆盖和隐藏

(1).成员函数被重载的特征:

a.具有相同的作用域(即同一个类定义中);

b.函数名字相同;

c.参数类型、顺序和数目不同(包括const参数和非const参数);

d.virtual关键字可有可无。

(2).成员函数被覆盖的特征:

a.不同的作用域(分别位于派生类和基类中);

b.函数名字相同;

c.参数列表完全相同;

d.基类函数必须是虚函数。

(3).成员函数被隐藏的特征:

a.派生类函数与基类的函数同名,但是参数列表有所差异。此时,无论有无virtual关键字,基类的函数在派生类中将被隐藏;

b.派生类的函数与基类的函数同名,参数列表也相同,但是基类函数没有virtual关键字。此时,基类的函数在派生类中被隐藏。

15.重载++

(1).前置++:int b = ++a;         <==>  a += 1;            int b = a;

(2).后置++:Int b = a++;         <==>int temp = a;      a += 1;     int b = temp;      temp:~int();

16.static成员函数不能定义为const的,这是因为static成员函数只是全局函数的一个形式的封装,而全局函数不存在const一说;何况static成员函数不能访问类的非静态成员(没有this指针),修改非静态数据成员又从何说起?

17.new/delete的各种用法

new/delete plain(普通new) nothrow(不抛出异常的new) placement(放置调用对象构造函数)
对象 new type_name; new(nothrow) type_name; new(p) type_name;
  delete p; delete p; delete p;
对象数组 new type_name[x]; new(nothrow) type_name[x]; new(p) type_name[x];
  delete []p; delete []p; delete []p;

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-20 11:43:24

《高质量程序设计指南C/C++语言》笔记总结的相关文章

《疯狂java-突破程序员基本功的16课 》笔记总结

本人最近读完<疯狂java-突破程序员基本功的16课 >读完后,感觉对java基础又有了新的认识,在这里总结一下:一.数组与内存控制 1.1 数组初始化 java语言的数组是静态的,即数组初始化之后,长度不可以变(区别,JavaScript数组可变,是动态的). 初始化分两种:静态初始化,初始化时由程序员指定每个数组元素的初始值,系统决定长度. 动态初始化,初始化时由程序员指定数组的长度,由系统指定默认值(int为0.boolean为false). 初始化=长度+初始值:注意不能同时指定长度又

10本Java精选书籍助你快速进阶Java顶尖程序员

书是人类进步的阶梯,从某种意义上讲,一个人读书多少,跟这个人将来能有多大成就取得多大成功有着必然的联系,然而读书不仅仅是求量的过程,还需要精读.有选择的读,前面的文章给大家介绍过从零基础学习java编程到精通之路的五本书籍,但是Java学习入门之后,想要往更高层次的Java方向发展,如果能有几本好书的辅助,可以使我们在Java进阶之路上事半功倍,那么下面亦是美网络小编再给大家推荐10本Java精选书籍助你进阶Java顶尖程序员. 1.<深入理解Java虚拟机:JVM高级特性与最佳实践> 本书从

工作2-5年java的程序员,这六个技术栈让你轻松涨薪50%

工作多年以及在面试中,我经常能体会到,有些面试者确实是认真努力工作,但坦白说表现出的能力水平却不足以通过面试,通常是两方面的原因: 1."知其然不知其所以然".做了多年技术,开发了很多业务应用,但似乎并未思考过种种技术选择背后的逻辑.坦白说,我并不放心把具有一定深度的任务交给他. 2.知识碎片化,不成系统.在面试中,面试者似乎无法完整.清晰地描述自己所开发的系统,或者使用的相关技术.平时可能埋头苦干,或者过于死磕某个实现细节,并没有抬头审视这些技术. 前人已经掉过的坑,后来的同学就别再

@Java web程序员,在保留现场,服务不重启的情况下,执行我们的调试代码(JSP 方式)

一.前言 类加载器实战系列的第六篇(悄悄跟你说,这篇比较水),前面5篇在这里: 实战分析Tomcat的类加载器结构(使用Eclipse MAT验证) 还是Tomcat,关于类加载器的趣味实验 了不得,我可能发现了Jar 包冲突的秘密 重写类加载器,实现简单的热替换 @Java Web 程序员,我们一起给程序开个后门吧:让你在保留现场,服务不重启的情况下,执行我们的调试代码 最近事不算多,所以有点时间写博客,昨天写着写着,测试的同学反馈说有一个bug.我看了下服务端日志,空指针了: 下面会给出详细

【转】成为Java顶尖程序员 ,看这11本书就够了

成为Java顶尖程序员 ,看这11本书就够了 转自:http://developer.51cto.com/art/201512/503095.htm 以下是我推荐给Java开发者们的一些值得一看的好书.但是这些书里面并没有Java基础.Java教程之类的书,不是我不推荐,而是离我自己学习 Java基础技术也过去好几年了,我学习的时候看的什么也忘了,所以我不能不负责任地推荐一些我自己都没有看过的书给大家. 作者:来源:攻城狮之家|2015-12-31 09:55 收藏 分享 “学习的最好途径就是看

Java Web 程序员的职业进阶之路

啥也不说了,都在图里了.希望可以给大家的职业规划一些提示,尤其是写了几年程序,却越来越迷茫的同学. Java Web 程序员的职业进阶之路,布布扣,bubuko.com

Java冠军程序员告诉你如何提升技术

让我们跟着兄弟连JavaEE培训 导师,聊一聊--怎样成为冠军程序员 ? 我认为以下几点能力是非常有帮助的: 1.强大的记忆力.当 我上八年级的时候,全因那位死气沉沉的历史老师,让我自己都相信我的记忆力太差了.然而,令我惊讶的是,历经多年程序员的工作,我惊奇地发现自己对之前看过的 Java 源代码可以立即重现.在我阅读 JDK 里的一个方法(method)的时侯,我常常能说出该方法(method)相较先前版本有无改动.也许我并不能记清楚某个人的长相和名字,但是对于 Java 代码可以做到过目不忘

突破程序员思维

突破程序员思维 来源:技匠的简书 过去我曾一直认为程序员是依靠他们的技术在编程,也是因为技术使得程序员的水平有高低之分,但随着我写代码的时间越来越长,也接触到更多的程序员,我渐渐发现程序员们其实是依靠他们所特有的程序员思维在进行编程的,而他们中的佼佼者正是那些有着更高思维成熟度的优秀程序员们. 什么是程序员思维 那么,什么是程序员思维呢?我曾读到过一些文章,试图给它下一个明确的定义,比如,具备抽象和逻辑思维的能力,拥有面向对象编程和设计的能力等等.我对这些所谓定义有些不以为然,因为,我所体会的程

[转]突破程序员思维

本文转自:https://kb.cnblogs.com/page/597877/ 过去我曾一直认为程序员是依靠他们的技术在编程,也是因为技术使得程序员的水平有高低之分,但随着我写代码的时间越来越长,也接触到更多的程序员, 我渐渐发现程序员们其实是依靠他们所特有的程序员思维在进行编程的,而他们中的佼佼者正是那些有着更高思维成熟度的优秀程序员们. 什么是程序员思维 那么,什么是程序员思维呢?我曾读到过一些文章,试图给它下一个明确的定义,比如,具备抽象和逻辑思维的能力,拥有面向对象编程和设计的能力等等

如何成为一名Java冠军程序员?

每一个稍微有点出息的人,都应该把行业里的前三名作为自己奋斗的目标和对手.你离成为冠军Java程序员还有多远,看完这篇你就知道了. 软件工程师的职业生涯里,知识有一个三年的半衰期.这意味着三年后,你所拥有的一半知识是毫无价值的.举这样一个例子,年纪越大,就越难看到有谁能够了解 TSR(内存驻留程序 Terminate-Stay-Resident ),而找到曾经写过一个 TSR的人更是如同大海捞针.在 DOS系统里,并不能让多个程序同时运行. 因此,在我们需要一个后台功能的时候,比如日历或计算器,我