《The C++ Programming Language》读书笔记

2.1 What is C++?

tour n.旅行

paradigm n.典范

with a bias towards systems programming 偏向于系统编程

This chapter explains what this means without going into the finer detailsof the language definition.(?)

plow on 继续向前

However, if you do skip part of this chapter,do yourself a favor byreturning to it later.(?)

Detailed understanding of language features - even of all features of alanguage - cannot compensate for lack of an overall view of the language andthe fundamental techniques for using it.

 

2.2 Programming Paradigms

unnecessarily adv.不必要地

elegantly adv.优美地

minimalism n.

 

 

第4章 类型和声明

 

4.1 类型

4.1.1 基本类型

对大部分应用而言,你可以简单地用bool表示逻辑值,用char表示字符,用int表示整数,用double表示浮点值。其他基本类型都是为优化或特殊需要而提供的变化,在真正需要它们之前最好是忽略之。当然必须知道它们,以便能够阅读已有的C和C++代码。

我真没听说过基本类型中还能区分最最基本类型(即最最基本的类型是bool,char,int,double),作者告诉我们应该优先使用最最基本类型,当然也就包含优先使用int了,但是int所占内存的大小和机器字有关,我记得有的书上说让我们尽量使用固定长度的类型,我究竟该相信谁呢?

 

4.2 布尔量

按照定义,true具有值1,而false具有值0.(当bool向int转换时,就是这样的值转的,好像这样转的场景不多。最好也别这样,在真假的定义中,有些库中的真不一定就是1,有可能是-1)

 

与此对应,整数可以隐式地转换到bool值:非零的整数转换为true,而0转换为false。(按作者的说法,我还意味这样的转换连告警都没有呢,但是,我在VS2005中,测试了一下:

    bool b1 = 7;   // warning C4305: 初始化: 从intbool截断

 

    // warning C4305: 初始化: 从int *__w64 bool截断

    // warning C4800: int *__w64 : 将值强制为布尔值truefalse(性能警告)

    int i = 3;

    bool b2 = &i; 

 

MSDN上建议的做法(即可以向表达式中添加!=0,它可使表达式成为 bool 类型),这样就不会有告警了:

    bool b1= (7 != 0);

 

    int i = 3;

    bool b2 = (&i != 0);

 

4.3 字符类型

在C语言里,wchar_t是一个typedef而不是一个内部类型。加上后缀_t就是为了区分标准typedef类型。

标准typedef类型有什么特征?

 

4.3.1 字符文字常量

 

4.4 整数类型

unsigned整数类型对于将存储看着是二进制数组的使用方式非常理想.采用unsigned而不用int,以便多获得一个位去表示正整数,就不是什么好主意.通过将变量声明为unsigned以保证某些值始终为正的企图,常常会被隐式转换规则击败.

那我该怎么办呢?为什么程序中还大量使用unsigned呢?例如WORD,DWORD之类的

 

4.4.1

It is a good idea to limit the use of nonobvious constants to a fewwell-commented const (§5.4) or enumerator (§4.8) initializers.

也就是说,最好在整数常量后加上修饰L,U等,以让编译器知道这个整数常量具体是什么类型.

有这个必要吗?

 

4.5 浮点类型

浮点常量默认是double类型.

 

4.6 大小

C++基本类型的某些方面是由实现确定的,例如int的大小.(好好利用sizeof,它不会骗我们)

 

People who program on a variety of systems or use a variety of compilerscare a lot because if they don’t, they are forced to waste timefinding and fixing obscure bugs.

 

It is relatively easy to limit the impact of implementationdependentlanguage features. Limiting the impact of systemdependent library facilities isfar harder. Using standard library facilities wherever feasible is oneapproach.

 

因为是由实现确定的,所以char,int等的大小在不同的系统上可能是不同的,你不能假定它就是多少字节,例如就存在char类型占32位的机器.

 

When needed, implementation-dependent aspects about an implementation canbe found in <limits>. For example:

#include <limits>

int main()

{

cout << "largest float == " << numeric_  limits<float>: :max()

<< ", char is signed == " << numeric_  limits<char>::is_signed << '\n';

}

上面的话是什么意思,是指如果我们使用<limits>就一定是对的吗?

 

4.7 void

 

4.8 枚举

 

一个整数值可以显式第转换到一个枚举值。除非这种转换的结果位于该枚举值的范围之内,否则结果则就是无定义的。例如:

enum flag{x=1, y =2 , z = 4, e = 8};

 

flag f = flag(99); // 无定义:不在flag的范围内

 

这个赋值说明为什么不允许隐式地从整数转换到枚举:大部分整数值在特定的枚举里都没有对应的表示。

 

The sizeof an enumeration is the sizeof some integral type that can holdits range and not larger

than sizeof(int), unless an enumerator cannot be represented as an int oras an unsigned int. For

example, sizeof(e1) could be 1 or maybe 4 but not 8 on a machine wheresizeof(int)==4.

 

By default, enumerations are converted to integers for arithmeticoperations. An enumeration is a userdefined

type, so users can define their own operations, such as ++ and <<for an enumeration.(这么先进的干法没玩过?

 

4.9 声明

4.9.1 声明的结构

4.9.2 声明多个名字

int* p, y; // int* p;int y;不是int* y;

由此说来,*其实更偏重于变量名一些,所以有些人写的时候,将*紧挨变量名也是说得通的.

4.9.3 名字

用于较大作用域的名字应该是相对比较长的更加明确的名字.例如Window_with_border和Department_number.然而,如果在很小的作用域里只使用那些短小而熟悉的名字,如x,i和p,代码则显得更清晰.(真的吗?试试)

让那些频繁使用的名字相对比较短,将较长的名字保留给不常用的实体,这种做法也很有价值.

名字的选择应该反映一个实体的意义,而不是它的实现.例如phone_book就比number_list好,即使这些号码实际存放在一个list里.选择名字也是一种艺术.(为什么?)

应设法保持一种统一的命名风格.

当然,统一用法是很难做到的,因为程序常常由一些片段组成,它们有不同的来源,采用的是不同的也都合理的风格.

 

4.9.4 作用域

 

没有办法去使用被屏蔽的局部名字,例如

int x = 1;

 

int main()

{

    int x = 2;

 

    {

        int x = 3;

 

        cout<< "x =" << x << endl;

        cout<< "::x =" << ::x << endl; // 永远无法使用int x = 2;

    }

 

    return 0;

}

Hiding names is unavoidable when writing large programs. However, a humanreader can easily

fail to notice that a name has been hidden.

尽量避免名字被屏蔽的情况!

 

注意这种情况:

class CTest

{

public:

    CTest()

    {

        i = 10;

    }

    void fun(int i)

    {

        cout << i;  //19

    }

 

    int i;

};

 

int main()

{

   

    CTest t;

    t.fun(19);

 

    _tsystem(_T("pause"));

 

    return 0;

}

 

4.9.5 初始化

 

4.9.6 对象和左值

 

4.9.7 typedef

有两个作用,一个是给比较笨拙的类型名定义一个缩写别名,一个是为了方便移植。

 

4.10 忠告

1.保持较小的作用域.毫无争议

2.不要在一个作用域和它外围作用域里采用同样的名字.即防止名字被屏蔽.毫无争议

3.在一个声明中(只)声明一个名字。即不要int* x,y;其实感觉有时可以用用,可以少占一行?

4.让常用的和局部的名字比较短,让不常用的和全局的名字比较长.在实践中怎么做?

5.避免看起来类似的名字,比如l1和ll,l0和lO等.这个好说

6.维持某种统一的命名风格.怎么做到?

7.仔细选择名字,反映其意义而不是反映其实现方式.为什么?

8.如果所用的内部类型表示某种可能变化的值,请用typedef为它定义一个有意义的名字。例如?

9.用typedef为类型定义同义词,用枚举或类去定义新类型.这个没啥争议的.

10.切记每个声明中都必须描述一个类型(没有隐式的"int").这个基本上不用管,我们没那么啥,编译器也没那么啥!

11.避免有关字符数值的不必要假设.因为长度不一定是1个字节

12.避免有关整数大小的不必要假设.即不要假定它一定占4个字节

13.避免有关浮点类型表示范围的不必要假设.因为长度不一定是8个字节

对于11,12,13,通过sizeof和库中的定义去避免假定

14.优先使用普通的int而不是short int或者long int.为什么?

15.优先使用double而不是float或者long double.为什么?

16.优先使用普通的char而不是signed char或者unsigned char.为什么?

17.避免做出有关对象大小的不必要假设.即不要假定char占一个字节,int占4个字节,double占8个字节

18.避免无符号算术.为什么?

19.应该带着疑问去看待从signed到unsigned,或者从unsigned到signed的转换.为什么会出现这种情况?

20.应该带着疑问去看待从浮点到整数的转换.例int j = 4.5; //4

    int k = -3.8; // -3

为什么会出现这种情况?

21.应该带着疑问去看待向小类型的转换,如将int转换为char.为什么会出现这种情况?

 

4.11 练习

1.略

2.略

3.略

4.略

5.略

6.略

7.略

 

第5章 指针、数组和结构

 

5.1 指针

5.1.1 零

在C中流行的是用一个宏NULL表示0指针。由于C++收紧的类型检查规则,采用普通的0而不是一些人建议的NULL宏,带来的问题会更少一些。如果你感到必须定义NULL,请采用

const int NULL = 0;

用const限定词是为防止无意地重新定义NULL,并保证NULL可以用到那些要求常量的地方。

 

究竟用什么好?

 

 

5.2 数组

5.2.1 数组初始化

5.2.2 字符串常量

 

const char* p1 = "123";

const char* p2 = "123";

 

void g()

{

    if(p1 == p2) // 结果是由编译器实现确定的。在VS2005中是相等的

    {

        cout << "One\n";

    }

}

 

sizeof("Bohr") == 5

字符串常量的类型是"适当个数的const字符的数组",所以"Bohr"的类型就是const char[5].(以前都知道,刚才又混了,还以为字符串常量的类型是指针类型,那么sizeof字符串常量相当于是sizeof字符指针,错错错!)

 

5.3 到数组的指针

5.3.1 在数组里漫游

 

5.4 常量

5.4.1 指针和常量

能区分指向常量的指针和常量指针就行。

char* const cp;

const char* cp;

const char* cp;

 

5.5 引用

 

引用的主要用途是为了描述函数的参数和返回值,特别是为了运算符的重载。

 

引用的一种最明显的实现方式是作为一个常量指针,即指向一个东西后就不能再指向其他的。(除了被优化掉的情况,可以说完全一样,以前看过生成的汇编)

 

void increment(int& a){a++;}

为了提高程序的可读性,通常应该尽可能避免让函数去修改它们的参数。相反,你应该让函数明确地返回一个值,或者明确要求一个指针参数。因为increment(x);的记法形式不能给读程序的人有关x的值可能被修改的提示性信息。而increment(&x);形式的则可以。如果你硬是要传递引用参数在函数内被修改,那么这些函数的名字就应该给出其引用参数将被修改的强烈提示。

可是在现实中我们好像没有做到这一点,引用被过多地使用?

 

引用还可以用于定义一些函数,使它们既可以被用在赋值的左边,也可以用在右边。例CArray

TYPE& GetAt( INT_PTR nIndex );

const TYPE& GetAt( INT_PTR nIndex ) const;

 

    CArray<int> arr;

    arr.Add(1);

    arr.Add(2);

 

    arr.GetAt(0) = 3;

    int i = arr.GetAt(1);

// 编译通过

我好像很少这样使,都是采用的Get/Set函数方式?究竟该选如何选择呢?

 

我记得以前通过引用可以延长临时变量的生命周期呢,现在测试好像不行了?

CString& fun()

{

    CString strTemp(_T("1234"));

    return strTemp;

}

void CFefefDlg::OnButton1()

{

    // TODO: Addyour control notification handler code here

    CString&str1 = fun();

    AfxMessageBox(str1);//

}

 

void CFefefDlg::OnButton1()

{

    CString& str1 =fun();

    AfxMessageBox(str1);// 我记得以前可以打印出正确的值现在怎么不行了?

}

 

5.6 指向void的指针

int* pi2 = static_cast<int*>(pv);

这种形式的转换,从本质上来说是不安全的,丑陋的,所以这里所用的记法static_cast,也被设计成及其丑陋的样子.

 

void*的最重要的用途就是需要向函数传递一个指针,而又不能对对象的类型做任何假设.还有就是从函数返回它.

采用void*函数通常存在于系统中很低的层次里,在那里需要操作某些真实的硬件资源.

void* my_alloc(size_t n);

在系统中较高层次出现void*应该认为是可疑的.

 

到函数的指针和到成员的指针都不能赋值给void*.

 

5.7 结构

 

5.8 忠告

1.Avoid nontrivial pointer arithmetic.

2.当心,不要超出数组的边界去写.简单,谁都知道.

3.尽量使用0而不是NULL.有争议

4.尽量使用vector和valarray而不是数组.为什么我还是不习惯呢?

5.尽量使用string而不是以0结尾的char数组.这两者有共同点吗?

6.Minimizeuse of plain reference arguments.即最好只用const的引用.

7.避免void*,除了在某些低级的代码里

8.Avoidnontrivial literals (‘‘magic numbers’’) in code. Instead, define and usesymbolic constants

 

 

5.9 练习

 

1.

    int arrInt[4] = {1,2,3,4};

    int (&refArr)[4] = arrInt; //如何定义一个数组的引用,可以这样搞,先(&refArr),则refArr是引用了,再写其他的

引用一个数组有一个非常重要的用途,例

template <size_tcount>

void Print(int (&arr)[count])

{

    for(int i = 0; i < count; i++ )

    {

        cout << i<< endl;

    }

}

 

    int arr[] = {1, 2,3, 4};

    Print(arr);

通过模板和数组的引用,可以实现通过一个参数传递一个数组信息到函数中的目的.实现过程:

模板相当于一个宏,即Print函数首先被编译器替换成:

void Print(int (&arr)[4])

{

    for(int i = 0; i < 4; i++ )

    {

        cout << i<< endl;

    }

}

此时,因为是参数是数组引用,所以可以把arr传递给它,并且编译器还能检查数组元素个数是否匹配,不匹配则编译不能通过.思想是从参考gets函数的C++版本过来的.

 

    char*pcArr[3][10];

    char*(*p)[10] = pcArr; // 如何定义一个指向数组的指针,我多次搞成char*pcArr[10];结果编译出错,呵呵!对于可以指向数组名的,其类型一定要和数组的元素类型一致才行.

 

好好体会

 

2.我的机子上每个成员至少是4字节对齐的(只试了VC6.0)

    char c;   //&c == 0012F698

    short s;  //&s == 0012F694

跟struct中成员的对齐方式还有些不一样,注意了!

 

3.以前不太会用typedef定义新类型,今天突然发现一个诀窍: 你要定义一个什么类型,你就先用这个类型定义一个变量,然后再在前面加typedef关键字,此时原来的变量名就是新定义的类型名.例如我想定义一个函数指针类型,那么我就先定义一个函数指针变量:

int (*p)();

此时你在前面加上typedef,变量名就成为新定义的类型名了.

 

4.略

5.略

6.略

7.略

8.略

9.略

10.略

11.主要是要知道怎么从控制台读取单词,在C语言中可以使用gets函数,在C++中

    ws( cin);

    cin.width( 9 );

 

    char c[10];

    cin >> c;

    cout << c<< endl;

要是能藉此学会输入输出流的使用就非常值了。

 

输入输出中如何实现适应不同字符集?

 

12.

int GetTimes(basic_string<TCHAR>sBig, basic_string<TCHAR> sSmall)

{

    assert(sBig.length() >= 2);

    assert(sSmall.length() == 2);

 

    int nTimes = 0;

    int nPos = 0;

    while( (nPos = sBig.find(sSmall, nPos)) !=-1 )

    {

        nTimes++;

        nPos++;

    }

 

    return nTimes;

}

 

int GetTimes(TCHAR sBig[], TCHAR sSmall[])

{

    assert(_tcslen(sBig) >= 2);

    assert(_tcslen(sSmall) == 2);

   

    int nTimes = 0;

    for (int i = 0; sBig[i+1] != 0; i++)

    {

        if(sBig[i] == sSmall[0]&& sBig[i+1]== sSmall[1])

        {

            nTimes++;

        }

    }

 

    return nTimes;

}

 

从实现上来说两者的原理差不多,但是C语言风格的速度比C++风格的快几千上万倍(string的构造和查找都比较费时间),执行一次没感觉,当执行多次差距就出来了。当要求速度时,看来STL还是没法和自己写的C函数相比。

这个题没什么难度,如果我能藉此学会string类的使用就非常值了。

 

注意:没有使用string或wstring,而是使用basic_string<TCHAR>,这样就可以达到适应不同字符集。

 

13.略

 

第7章 函数

 

7.1 函数声明

7.1.1 函数定义

在函数的定义里,可以存在不使用的参数,这也是常能看到的情况:

void search(table* t, const char* key, const char*)

{

    // 第三个参数没有使用,即没有带参数名

}

如上所示,根本不使用的参数可以采用不予命名的方式明示。典型情况是,出现未命名参数的原因是做过代码的简化,或者是计划在将来做功能扩充。对于这两种情况,虽然不使用但是还是让参数留在那里,就能保证那些调用函数的地方不会受到修改的影响。

 

我在工作中真正遇到这种情况吗?举例

 

inline int fac(int n)

{

    return (n < 2) 1 : n*fac(n -1);

}

对于fac(6),某个编译器可能直接产生出常量720,另一个编译器可能是6*fac(5),或者有的编译器根本不能够内联。所以我说,讨论什么情况下函数会被内联没有多大的实际意义,最终还得依赖于编译器的实现,上次开会我们还讨论得那么使劲呢,无聊。

 

特别地,每个内联函数仍然会有自己的独立地址,内联函数里的那些static变量也将有自己的地址。

 

怎么判断一个函数是否真的被内联了?

 

7.1.2 静态变量

 

7.2 参数传递

字面量、常量和需要转换的参数都可以传递给const&参数,但不能传递给非const的引用参数。

void update(float& i);

 

void g(doubl d, float r)

{

    update(2.0f);// 错误:const参数

    update(r);    // 正确

    update(d);    // 错误:要求类型转换

}

如果允许所有这些调用,update()将会不声不响地去更新一个马上就会被删除的临时变量。这经常会让程序员极不愉快地大吃一惊。

 

7.2.1 数组参数

这儿提到传递数组时,如何将数组的元素个数传递进去的方式,一种是在数组最后一个元素设置一个特殊值,比如字符串的话就搞个'0',一种是多传递一个表示元素个数的参数进去。

 

作者的建议:再提一次,vector等类型可以用于代替内部的,低级的数组和指针等一类的东西。

 

但是,我觉得数组挺好用的,没有什么不好的,为什么我始终还是不能纠正这种观念?

 

7.3 返回值

 

7.4 重载函数名

7.4.1 重载和返回类型

7.4.2 重载与作用域

在不同的非名字空间作用域里声明的函数不算是重载。例如

void f(int);

void fun()

{

    voidf(double);

    f(1); // 虽然最匹配的是,但实际上调用的是voidf(double)

}

 

7.5 默认参数

 

7.6 未确定数目的参数

略。以前曾经有过总结,把它找出来看看就行。要做变参的时候参考C运行时库中的或MFC中的变参函数就行。

 

7.7 指向函数的指针

对于函数只能做两件事:调用它,或者取得它的地址。

 

7.8 宏

关于宏的第一规则是:绝不应该去使用它,除非你不得不这样做。

 

宏其实可以做一些特别的事情,参考一下cppunit中的宏,学习一些宏技巧?

 

7.8.1 条件编译

 

 

 

7.9 忠告

1.质疑那些非const的引用参数;如果你想要一个函数去修改其参数 ,请使用指针或返回值。

2.当你需要尽可能减少参数复制时,应该使用const引用参数。

3.广泛而一致地使用const。

4.避免不确定数目的参数。

5.不要返回局部变量的指针或者引用。

6.当一些函数对不同的类型执行概念上相同的工作时,请使用重载。

7.在各种整数上重载时,通过提供函数去消除常见的歧义性。

8.在考虑使用指向函数的指针时,请考虑使用虚函数或模板是不是更好的选择。

9.如果你必须使用宏,请使用带有许多大写字母的丑陋的名字。

 

7.10 练习

1.略

2.略

3.略

4.略

5.略

6.qsort()是如何实现的?

7.如何实现一个map?

8.写一个函数求二维数组的逆?

9.

10.

11.

12.略

13.略

14.略

15.略

16.略

17.略

18.略

19.略

20.略

 

第8章 名字空间和异常

 

8.4 忠告

1.用名字空间表示逻辑结构

2.将每个非局部的名字放入某个名字空间里,除了main之外

3.名字空间的设计应该让你能很方便地使用它,而又不会意外地访问了其他的无关名字空间

4.避免对名字空间使用很短的名字

5.如果需要,通过名字空间别名去缓和长名字空间名的影响

6.避免给你的名字空间的用户添加太大的记法负担

7.在定义名字空间的成员时使用namesapce::member的形式

8.只在转换时,或者在局部作用域时,采用using namespace

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值