读书笔记之:C/C++代码精髓

这是阅读 《C/C++代码精髓》的记录。这本书其实更偏向于讲解C++中的相关知识。也介绍了很多C与C++的区别联系。

第1章 更好的C
1.简介
  C++是一个混合语言,它将面向对象特征与系统编程语言混合在一起。
  C语言的兼容性是C++设计的一个主要的目标,1989年Bjarne在ANSI C++委员会的主题演讲中提到,C++是“工程上的妥协”,并且必须使它“越接近C越好,但不能过度”。
  C++是多范例语言:像C和Pascal一样支持过程编程,像Ada支持数据抽象和通用性(模板),像其他面向对象一样,支持继承和多态。
2. 更好的C
C++比C更安全,更富于表达,所以可以将它作为更好的C使用:类型安全链接,强制的函数原型,内嵌函数,const限定修饰符,函数重载,缺省参数,引用,动态内存管理。
C++中用户自定义类型也允许隐式转换,然而在任一个转换序列中只允许有一个隐式用户定义的转换。
可以通过声明单参数构造函数explicit来防止隐式转换。

 C++中,explicit关键字用来修饰类的构造函数,被修的构造函数的,不能生相转换,只能以示的方式转换

explicit使用注意事:

  *  explicit 关键字只能用于内部的构造函数声明上。

  *  explicit 关键字作用于个参数的构造函数。

下面是一个比好的例子:

 class Circle

 {

 public:

     Circle(double r) : R(r) {}

     Circle(int x, int y = 0) : X(x), Y(y) {} 

 Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {}

 private:

     double R;

     int    X;

     int    Y;

 };

 int _tmain(int argc, _TCHAR* argv[])

 {

 //转换 

 //编译器会将它成如下代

 //tmp = Circle(1.23)

 //Circle A(tmp);  

 //tmp.~Circle();

     Circle A = 1.23;  

 //注意是int型的,用的是Circle(int x, int y = 0) 

 //然有2个参数,但后一个有默认值,任然能转换

     Circle B = 123;

 //个算用了拷构造函数

     Circle C = A;    

     return 0;

 }

加了explicit关键字后,可防止以上转换发

 class Circle

 {

 public:

     explicit Circle(double r) : R(r) {}

   explicit Circle(int x, int y = 0) : X(x), Y(y) {} 

     explicit Circle(const Circle& c) : R(c.R), X(c.X), Y(c.Y) {}

 private:

     double R; 

     int    X; 

     int    Y;

 }; 

 

 3. C++中的IO

 

一个从流中读取的函数称为提取器,而一个输出函数称为插入器。如
 main()
 {
     char c;
     while(cin.get(c))
         cout.put(c);
 }
 其中,get提取器从流中把下一个字节存放在char引用参数中。
 getline也是提取器,>>就是一个插入器。
 流输入输出中的格式化标志位是位掩码值,可以使用setf来设置,使用unsetf来复位。
 使用presion函数来指定显示的小数的位数。
 如:
 cout.presion(3);这样就设置了在输出中使用3位小数。
 cout.setf(ios::oct,ios::basefield);设置以8进制输出。
 C++中的endl是一个操纵器,还包括其他许多操纵器,可以实现与setf类似的功能。

C++中的运算符重,主要是包括全局函数和成函数两

C++C 的兼容性

(1). C++中全局const声明默的是内部接,而在C 中却是外部接,所以C++,可以在一个文件中使用const来取代#define宏定,如果希望一个const象具有外部接特性,就必使用extern关键字。

(2).在c中可以将任意型的指指向空void*,或将指从空void*指向任何其他型,而在C++中不允在没有转换类型的情况下将一个空型指指向其他型。

第2章 指针

C++中指针与数组

向整数中压缩数据date=(year<<9)|(month<<5)|day;

struct 位域构:

struct Date

{

unsigned day:5;

unsigned month:4;

unsigned year:7;

}

如果要使用上述构来表示一个整型数的,只需将指向整型的指针强指向Date的指

(1)一个位域必在同一个字中,不能跨两个字。如一个字所剩空存放另一位域从下一元起存放位域。也可以有意使某位域从下一始。(2)由于位域不允跨两个字,因此位域的度不能大于一个字度,也就是不能超8位二位。(3)位域可以无位域名,这时它只用来作填充或整位置。无名的位域是不能使用的。

 
向成员函数的指针

 

 

 第3章 预处理

  1. C编译器被允许为标准C库中的任何函数提供宏形式(如getc和putc为了效率经常被当作宏来用)。库函数中的宏形式在实际编译时隐藏其原型,所以其参数在翻译时不进行类型检测。为了加强调用真正的函数,用#undef指令去掉宏定义。如:
#undef getc
 或者当调用时可以把函数名用括号括起来。
 C=(getc)(stdin);
2. 预处理运算符
defined运算符
#if defined(X) 等价于 #ifdef X
#if !defined(X) 等价于 #ifndef X
但defined运算符更加灵活。
#运算符是字符串化,将其后面的解释为字符串
## 加标记:将两个标记连结在一起形成单个标记
3. assert宏的实现
#define assert(cond) ((cond)?(void)0:_assert(#cond,_FILE_,_LINE_))
void _assert(char*cond,char*filename,long lineno)
{
}
第4章 C标准库

 

 

 

第8章 模板
1. 模板特化
类模板特化,

#include <iostream>
using std::cout;
template<typename T>
class A
{
public:
    A(){cout<<"primayr\n";}
};
template<>
class A<char>
{
public:
    A(){cout<<"char specialization\n";}
};
template<>
class A<float>
{
    public:
    A(){cout<<"float specialization\n";}
};
int main()
{
    A<int> a1;
    A<char> a2;
    A<float> a3;
}

函数模板特化,

#include <iostream>
#include <string>
#include <cstring>
using std::cout;
using std::endl;
using std::string;
template<class T>
size_t bytes(T& t)
{
    cout<<"using primary templates\n";
    return sizeof t;
}
size_t bytes(char*& s)
{
    cout<<"using char* overload\n";
    return strlen(s)+1;
}
template<>
size_t bytes<string>(string&s)
{
    cout<<"using string explicit specialization\n";
    return sizeof s;
}
template<>
size_t bytes<float>(float &x)
{
    cout<<"using float explicit specilization\n";
    return sizeof x;
}
int main()
{
    int i;
    cout<<"bytes in i:"<<bytes(i)<<endl;
   char *s="hello";
    cout<<"bytes in s:"<<bytes(s)<<endl;
   string t("very good");
    cout<<"bytes in t:"<<bytes(t)<<endl;
   float y;
    cout<<"bytes in y:"<<bytes(y)<<endl;
    return 0;
}

部分类模板特化

#include <iostream>
using std::cout;
template<class T,class U>
class A
{
    public:
    A(){cout<<"primary template\n";}
};
template<class T,class U>
class A<T*,U>
{
    public:
    A(){cout<<"<T*,U> partial specialization\n";}
};
template<class T>
class A<T,T>
{
    public:
    A(){cout<<"<T,T> partial specialization\n";}
};
template<class U>
class A<int,U>
{
    public:
    A(){cout<<"<int,U> partial specialization\n";}
};
int main()
{
    A<char,int >a1;
    A<int*,float>a2;
    A<double,double>a3;
    A<int,char>a4;
    return 0;
}


第9章 位操作
1.用于无符号整整的位函数
#define SET(n,i)        ((n)|(1u<<i)) //置1
#define RESET(n,i)      ((n)&~(1u<<i)) //复位
#define TOGGLE(n,i)     ((n)^(1u<<i)) //取反
#define TEST(n,i)       !!((n)&(1u<<i)) //测试

2. vector<bool>模板特化
该容器经过优化后用来存储位,它从左到右的定向,被认为是一个动态分配字节长度的位字符串
bitset类模板支持有效的固定大小的位设置操作
vector<bool>模板规范支持动态大小的位字符串


第14章 面向对象编程
1. 虚函数与纯虚函数
vptr,vtbl
其实,编译器在编译时并不知道要调用的函数体的正确位置,但它插入了一段能找到正确的函数体的代码。这称之为晚捆绑(late binding)或运行时捆绑(runtime binding) 技术。
通过virtual 关键字创建虚函数能引发晚捆绑,编译器在幕后完成了实现晚捆绑的必要机制。它对每个包含虚函数的类创建一个表(称为VTABLE),用于放置虚函数的地 址。
在每个包含虚函数的类中,编译器秘密地放置了一个称之为vpointer(缩写为VPTR)的指针,指向这个对象的VTABLE。所以无论这个对象包 含一个或是多少虚函数,编译器都只放置一个VPTR即可。
VPTR由编译器在构造函数中秘密地插入的代码来完成初始化,指向相应的VTABLE,这样对象 就“知道”自己是什么类型了。 VPTR都在对象的相同位置,常常是对象的开头。这样,编译器可以容易地找到对象的VTABLE并获取函数体的地址。
如果我们用sizeof查看前面Base类的长度,我们就会发现,它的长度不仅仅是一个int的长度,而是增加了刚好是一个void指针的长度(在我的机器里面,一个int占4个字节,一个void指针占4个字节,这样正好类Base的长度为8个字节)。
每当创建一个包含虚函数的类或从包含虚函数的类派生一个类时,编译器就为这个类创建一个唯一的VTABLE。在VTABLE中,放置了这个类中或是它的 基类中所有虚函数的地址,这些虚函数的顺序都是一样的,所以通过偏移量可以容易地找到所需的函数体的地址。假如在派生类中没有对在基类中的某个虚函数进行 重写(overriding),那末还使用基类的这个虚函数的地址(正如上面的程序结果所示)。

第15章 算法