1、一些C++基础知识
模板类string的设计属于底层,其中运用到了很多C++的编程技巧,比如模板、迭代器、友元、函数和运算符重载、内联等等,为了便于后续理解string类,这里先对涉及到的概念做个简单的介绍。C++基础比较扎实的童鞋可以直接跳到第三节。
1.1 typedef
1.1.1 四种常见用法
- 定义一种类型的别名,不只是简单的宏替换。可用作同时声明指针型的多个对象
- typedef char* PCHAR;
- PCHAR pa, pb; // 同时声明两个char类型的指针pa和pb
- char* pa, pb; // 声明一个指针(pa)和一个char变量(pb)
- // 下边的声明也是创建两个char类型的指针。但相对没有typedef的形式直观,尤其在需要大量指针的地方
- char *pa, *pb;
复制代码 顺便说下,*运算符两边的空格是可选的,在哪里添加空格,对于编译器来说没有任何区别。- char *pa; // 强调*pa是一个char类型的值,C中多用这种格式。
- char* pa; // 强调char*是一种类型——指向char的指针。C++中多用此种格式。另外在C++中char*是一种复合类型。
复制代码 在旧的C代码中,声明struct新对象时,必须要带上struct,形式为:struct 结构名 对象名。- 1 // 定义
- 2 struct StudentStruct
- 3 {
- 4 int ID;
- 5 string name;
- 6 };
- 7 // C声明StudentStruct类型对象
- 8 struct StudentStruct s1;
复制代码 使用typedef来定义结构体StrudentStruct的别名为Student,声明的时候就可以少写一个struct,尤其在声明多个struct对象时更加简洁直观。如下:- 1 // 定义
- 2 typedef struct StudentStruct
- 3 {
- 4 int ID;
- 5 string name;
- 6 }Student;
- 7 // C声明StudentStruct类型对象s1和s2
- 8 Student s1, s2;
复制代码 而在C++中,声明struct对象时本来就不需要写struct,其形式为:结构名 对象名。- // C++声明StudentStruct类型对象s1和s2
- StudentStruct s1,s2;
复制代码 所以,在C++中,typedef的作用并不大。了解他便于我们阅读旧代码。
比如定义一个REAL的浮点类型,在目标平台一上,让它表示最高精度的类型为:- typedef long double REAL;
复制代码 在不支持long double的平台二上,改为: 在连double都不支持的平台三上,改为: 也就是说,在跨平台时,只要改下typedef本身就行,不要对其他源码做任何修改。
标准库中广泛使用了这个技巧,比如size_t、intptr_t等- 1 // Definitions of common types
- 2 #ifdef _WIN64
- 3 typedef unsigned __int64 size_t;
- 4 typedef __int64 ptrdiff_t;
- 5 typedef __int64 intptr_t;
- 6 #else
- 7 typedef unsigned int size_t;
- 8 typedef int ptrdiff_t;
- 9 typedef int intptr_t;
- 10 #endif
复制代码 在阅读代码的过程中,我们经常会遇到一些复杂的声明和定义,例如:- 1 // 理解下边这种复杂声明可用“右左法则”:
- 2 // 从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。
- 3
- 4 // 例1
- 5 void* (*(*a)(int))[10];
- 6 // 1、找到变量名a,往右看是圆括号,调转方向往左看到*号,说明a是一个指针;
- 7 // 2、跳出内层圆括号,往右看是参数列表,说明a是一个函数指针,接着往左看是*号,说明指向的函数返回值是指针;
- 8 // 3、再跳出外层圆括号,往右看是[]运算符,说明函数返回的是一个数组指针,往左看是void*,说明数组包含的类型是void*。
- 9 // 简言之,a是一个指向函数的指针,该函数接受一个整型参数并返回一个指向含有10个void指针数组的指针。
- 10
- 11 // 例2
- 12 float(*(*b)(int, int, float))(int);// 1、找到变量名b,往右看是圆括号,调转方向往左看到*号,说明b是一个指针;
- 13 // 2、跳出内层圆括号,往右看是参数列表,说明b是一个函数指针,接着往左看是*号,说明指向的函数返回值是指针;
- 14 // 3、再跳出外层圆括号,往右看还是参数列表,说明返回的指针是一个函数指针,该函数有一个int类型的参数,返回值类型是float。
- 15 // 简言之,b是一个指向函数的指针,该函数接受三个参数(int, int和float),且返回一个指向函数的指针,该函数接受一个整型参数并返回一个float。
- 16
- 17 // 例3
- 18 double(*(*(*c)())[10])();
- 19 // 1、先找到变量名c(这里c其实是新类型名),往右看是圆括号,调转方向往左是*,说明c是一个指针;
- 20 // 2、跳出圆括号,往右看是空参数列表,说明c是一个函数指针,接着往左是*号,说明该函数的返回值是一个指针;
- 21 // 3、跳出第二层圆括号,往右是[]运算符,说明函数的返回值是一个数组指针,接着往左是*号,说明数组中包含的是指针;
- 22 // 4、跳出第三层圆括号,往右是参数列表,说明数组中包含的是函数指针,这些函数没有参数,返回值类型是double。
- 23 // 简言之,c是一个指向函数的指针,该函数无参数,且返回一个含有10个指向函数指针的数组的指针,这些函数不接受参数且返回double值。
- 24
- 25 // 例4
- 26 int(*(*d())[10])(); // 这是一个函数声明,不是变量定义
- 27 // 1、找到变量名d,往右是一个无参参数列表,说明d是一个函数,接着往左是*号,说明函数返回值是一个指针;
- 28 // 2、跳出里层圆括号,往右是[]运算符,说明d的函数返回值是一个指向数组的指针,往左是*号,说明数组中包含的元素是指针;
- 29 // 3、跳出外层圆括号,往右是一个无参参数列表,说明数组中包含的元素是函数指针,这些函数没有参数,返回值的类型是int。
- 30 // 简言之,d是一个返回指针的函数,该指针指向含有10个函数指针的数组,这些函数不接受参数且返回整型值。
复制代码 如果想要定义和a同类型的变量a2,那么得重复书写:- void* (*(*a)(int))[10];
- void* (*(*a2)(int))[10];
复制代码 那怎么避免这种没有价值的重复呢?答案就是用typedef来简化复杂的声明和定义。- // 在之前的定义前边加typedef,然后将变量名a替换为类型名A
- typedef void* (*(*A)(int))[10];
- // 定义相同类型的变量a和a2
- A a, a2;
复制代码 typedef在这里的用法,总结一下就是:任何声明变量的语句前面加上typedef之后,原来是变量的都变成一种类型。不管这个声明中的标识符号出现在中间还是最后。
1.1.2 使用typedef容易碰到的陷进
typedef定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如:- typedef char* PSTR;
- int mustrcmp(const PSTR, const PSTR);
复制代码 上边的const PSTR并不是const char*,而是相当于.char* const。原因在于const给予了整个指针本身以常量性,也就是形成常量指针char* const。
typedef在语法上是一个存储类的关键字(和auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性。如: 编译会报错:“error C2159:指定了一个以上的存储类”。
1.2 #define
#define是宏定义指令,宏定义就是将一个标识符定义为一个字符串,在预编译阶段执行,将源程序中的标志符全部替换为指定的字符串。#define有以下几种常见用法:
格式:#define 标识符 字符串
其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。
格式:#define 宏名(形参表) 字符串- 1 #define add(x, y) (x + y) //此处要打括号,不然执行2*add(x,y)会变成 2*x + y
- 2 int main()
- 3 {
- 4 std::cout << add(9, 12) << std::endl; // 输出21
- 5 return 0;
- 6 }
复制代码 上例中,m_y是嵌套依赖名称,需要typename来告诉编译器Y是一个类型名,而非变量或其他。否则在T成为已知之前,是没有办法知道T::Y到底是不是一个类型。
- typename可在模板声明或定义中的任何位置使用任何类型。不允许在基类列表中使用该关键字,除非将它用作模板基类的模板自变量。
- #ifndef DATATYPE_H
- #define DATATYPE_H
- ...
- #endif
复制代码 1.6 template
C++提供了模板(template)编程的概念。所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的形参类型不具体指定,用一个虚拟的类型来代表。这种通用的方式称为模板。模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
1.6.1 函数模板
函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如int或double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型方式编程,因此又被称为通用编程。由于类型用参数表示,因此模板特性也被称为参数化类型(parameterized types)。
请注意,模板并不创建任何函数,而只是告诉编译器如何定义函数。一般如果需要多个将同一种算法用于不同类型的函数,可使用模板。
(1)模板定义 - 1 #ifdef WINDOWS
- 2 ...
- 3 (#else)
- 4 ...
- 5 #endif
- 6 #ifdef LINUX
- 7 ...
- 8 (#else)
- 9 ...
- 10 #endif
复制代码 在C++98添加关键字typename之前,用class来创建模板,二者在此作用相同。注意这里class只是表明T是一个通用的类型说明符,在使用模板时,将使用实际的类型替换它。
(2)显式具体化(explicit specialization)
- 对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本。
- 他们的优先级为:非模板 > 具体化 > 常规模板。
- 显示具体化的原型与定义应以template打头,并通过名称来指出类型。
举例如下:
[code] 1 #include 2 3 // 常规模板 4 template 5 void Swap(T &a, T &b); 6 7 struct job 8 { 9 char name[40];10 double salary;11 int floor;12 };13 14 // 显示具体化15 template void Swap(job &j1, job &j2);16 17 int main()18 {19 using namespace std;20 cout.precision(2); // 保留两位小数精度21 cout.setf(ios::fixed, ios::floatfield); // fixed设置cout为定点输出格式;floatfield设置输出时按浮点格式,小数点后有6为数字22 23 int i = 10, j = 20;24 Swap(i, j); // 生成Swap的一个实例:void Swap(int &, int&)25 cout |