【C++】从设计原理来看string类

打印 上一主题 下一主题

主题 826|帖子 826|积分 2478

1、一些C++基础知识

  模板类string的设计属于底层,其中运用到了很多C++的编程技巧,比如模板、迭代器、友元、函数和运算符重载、内联等等,为了便于后续理解string类,这里先对涉及到的概念做个简单的介绍。C++基础比较扎实的童鞋可以直接跳到第三节。
1.1 typedef

1.1.1 四种常见用法


  • 定义一种类型的别名,不只是简单的宏替换。可用作同时声明指针型的多个对象
  1. typedef char* PCHAR;
  2. PCHAR pa, pb;  // 同时声明两个char类型的指针pa和pb
  3. char* pa, pb;  // 声明一个指针(pa)和一个char变量(pb)
  4. // 下边的声明也是创建两个char类型的指针。但相对没有typedef的形式直观,尤其在需要大量指针的地方
  5. char *pa, *pb;  
复制代码
  顺便说下,*运算符两边的空格是可选的,在哪里添加空格,对于编译器来说没有任何区别。
  1. char *pa;  // 强调*pa是一个char类型的值,C中多用这种格式。
  2. char* pa;  // 强调char*是一种类型——指向char的指针。C++中多用此种格式。另外在C++中char*是一种复合类型。
复制代码

  • 定义struct结构体别名
  在旧的C代码中,声明struct新对象时,必须要带上struct,形式为:struct 结构名 对象名。
  1. 1 // 定义
  2. 2 struct StudentStruct
  3. 3 {
  4. 4     int ID;
  5. 5     string name;
  6. 6 };
  7. 7 // C声明StudentStruct类型对象
  8. 8 struct StudentStruct s1;
复制代码
  使用typedef来定义结构体StrudentStruct的别名为Student,声明的时候就可以少写一个struct,尤其在声明多个struct对象时更加简洁直观。如下:
  1. 1 // 定义
  2. 2 typedef struct StudentStruct
  3. 3 {
  4. 4     int ID;
  5. 5     string name;
  6. 6 }Student;
  7. 7 // C声明StudentStruct类型对象s1和s2
  8. 8 Student s1, s2;
复制代码
  而在C++中,声明struct对象时本来就不需要写struct,其形式为:结构名 对象名。
  1. // C++声明StudentStruct类型对象s1和s2
  2. StudentStruct s1,s2;
复制代码
  所以,在C++中,typedef的作用并不大。了解他便于我们阅读旧代码。

  • 定义与平台无关的类型
  比如定义一个REAL的浮点类型,在目标平台一上,让它表示最高精度的类型为:
  1. typedef long double REAL;
复制代码
  在不支持long double的平台二上,改为:
  1. typedef double REAL;
复制代码
  在连double都不支持的平台三上,改为:
  1. typedef float REAL;
复制代码
  也就是说,在跨平台时,只要改下typedef本身就行,不要对其他源码做任何修改。
  标准库中广泛使用了这个技巧,比如size_t、intptr_t等
  1. 1 // Definitions of common types
  2. 2 #ifdef _WIN64
  3. 3     typedef unsigned __int64 size_t;
  4. 4     typedef __int64          ptrdiff_t;
  5. 5     typedef __int64          intptr_t;
  6. 6 #else
  7. 7     typedef unsigned int     size_t;
  8. 8     typedef int              ptrdiff_t;
  9. 9     typedef int              intptr_t;
  10. 10 #endif
复制代码

  • 为复杂的声明定义一个新的简单的别名
  在阅读代码的过程中,我们经常会遇到一些复杂的声明和定义,例如:
  1. 1 // 理解下边这种复杂声明可用“右左法则”:
  2. 2 // 从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。
  3. 3
  4. 4 // 例1
  5. 5 void* (*(*a)(int))[10];
  6. 6 // 1、找到变量名a,往右看是圆括号,调转方向往左看到*号,说明a是一个指针;
  7. 7 // 2、跳出内层圆括号,往右看是参数列表,说明a是一个函数指针,接着往左看是*号,说明指向的函数返回值是指针;
  8. 8 // 3、再跳出外层圆括号,往右看是[]运算符,说明函数返回的是一个数组指针,往左看是void*,说明数组包含的类型是void*。
  9. 9 // 简言之,a是一个指向函数的指针,该函数接受一个整型参数并返回一个指向含有10个void指针数组的指针。
  10. 10
  11. 11 // 例2
  12. 12 float(*(*b)(int, int, float))(int);// 1、找到变量名b,往右看是圆括号,调转方向往左看到*号,说明b是一个指针;
  13. 13 // 2、跳出内层圆括号,往右看是参数列表,说明b是一个函数指针,接着往左看是*号,说明指向的函数返回值是指针;
  14. 14 // 3、再跳出外层圆括号,往右看还是参数列表,说明返回的指针是一个函数指针,该函数有一个int类型的参数,返回值类型是float。
  15. 15 // 简言之,b是一个指向函数的指针,该函数接受三个参数(int, int和float),且返回一个指向函数的指针,该函数接受一个整型参数并返回一个float。
  16. 16
  17. 17 // 例3
  18. 18 double(*(*(*c)())[10])();
  19. 19 // 1、先找到变量名c(这里c其实是新类型名),往右看是圆括号,调转方向往左是*,说明c是一个指针;
  20. 20 // 2、跳出圆括号,往右看是空参数列表,说明c是一个函数指针,接着往左是*号,说明该函数的返回值是一个指针;
  21. 21 // 3、跳出第二层圆括号,往右是[]运算符,说明函数的返回值是一个数组指针,接着往左是*号,说明数组中包含的是指针;
  22. 22 // 4、跳出第三层圆括号,往右是参数列表,说明数组中包含的是函数指针,这些函数没有参数,返回值类型是double。
  23. 23 // 简言之,c是一个指向函数的指针,该函数无参数,且返回一个含有10个指向函数指针的数组的指针,这些函数不接受参数且返回double值。
  24. 24
  25. 25 // 例4
  26. 26 int(*(*d())[10])();  // 这是一个函数声明,不是变量定义
  27. 27 // 1、找到变量名d,往右是一个无参参数列表,说明d是一个函数,接着往左是*号,说明函数返回值是一个指针;
  28. 28 // 2、跳出里层圆括号,往右是[]运算符,说明d的函数返回值是一个指向数组的指针,往左是*号,说明数组中包含的元素是指针;
  29. 29 // 3、跳出外层圆括号,往右是一个无参参数列表,说明数组中包含的元素是函数指针,这些函数没有参数,返回值的类型是int。
  30. 30 // 简言之,d是一个返回指针的函数,该指针指向含有10个函数指针的数组,这些函数不接受参数且返回整型值。
复制代码
  如果想要定义和a同类型的变量a2,那么得重复书写:
  1. void* (*(*a)(int))[10];
  2. void* (*(*a2)(int))[10];
复制代码
  那怎么避免这种没有价值的重复呢?答案就是用typedef来简化复杂的声明和定义。
  1. // 在之前的定义前边加typedef,然后将变量名a替换为类型名A
  2. typedef void* (*(*A)(int))[10];
  3. // 定义相同类型的变量a和a2
  4. A a, a2;
复制代码
  typedef在这里的用法,总结一下就是:任何声明变量的语句前面加上typedef之后,原来是变量的都变成一种类型。不管这个声明中的标识符号出现在中间还是最后。
1.1.2 使用typedef容易碰到的陷进


  • 陷进一
  typedef定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如:
  1. typedef char* PSTR;
  2. int mustrcmp(const PSTR, const PSTR); 
复制代码
  上边的const PSTR并不是const char*,而是相当于.char* const。原因在于const给予了整个指针本身以常量性,也就是形成常量指针char* const。

  • 陷进二
  typedef在语法上是一个存储类的关键字(和auto、extern、mutable、static、register等一样),虽然它并不真正影响对象的存储特性。如:
  1. typedef static int INT2; 
复制代码
  编译会报错:“error C2159:指定了一个以上的存储类”。
1.2 #define

  #define是宏定义指令,宏定义就是将一个标识符定义为一个字符串,在预编译阶段执行,将源程序中的标志符全部替换为指定的字符串。#define有以下几种常见用法:

  • 无参宏定义
  格式:#define 标识符 字符串
  其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。

  • 有参宏定义
  格式:#define 宏名(形参表) 字符串
  1. 1 #define add(x, y) (x + y)   //此处要打括号,不然执行2*add(x,y)会变成 2*x + y
  2. 2 int main()
  3. 3 {
  4. 4     std::cout << add(9, 12) << std::endl;  // 输出21
  5. 5     return 0;
  6. 6 }
复制代码
  上例中,m_y是嵌套依赖名称,需要typename来告诉编译器Y是一个类型名,而非变量或其他。否则在T成为已知之前,是没有办法知道T::Y到底是不是一个类型。

  • typename可在模板声明或定义中的任何位置使用任何类型。不允许在基类列表中使用该关键字,除非将它用作模板基类的模板自变量。
  1. #ifndef DATATYPE_H
  2. #define DATATYPE_H
  3. ...
  4. #endif
复制代码
1.6 template

  C++提供了模板(template)编程的概念。所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的形参类型不具体指定,用一个虚拟的类型来代表。这种通用的方式称为模板。模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
1.6.1 函数模板

  函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型(如int或double)替换。通过将类型作为参数传递给模板,可使编译器生成该类型的函数。由于模板允许以泛型方式编程,因此又被称为通用编程。由于类型用参数表示,因此模板特性也被称为参数化类型(parameterized types)。
  请注意,模板并不创建任何函数,而只是告诉编译器如何定义函数。一般如果需要多个将同一种算法用于不同类型的函数,可使用模板。
(1)模板定义  
  1. 1 #ifdef WINDOWS
  2. 2 ...
  3. 3 (#else)
  4. 4 ...
  5. 5 #endif
  6. 6 #ifdef LINUX
  7. 7 ...
  8. 8 (#else)
  9. 9 ...
  10. 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
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

张国伟

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表