【C++】C++基础记录(一)

【C++】C++基础记录(一)

本文记录了我学习C++的一些基础条目知识

HelloWorld

#include <iostream>

int main()
{
    std::cout << "Hello World" << std::endl;
    return 0;
}

命名空间

名称空间是给代码指定的名称,有助于降低命名冲突的风险。通过使用 std::cout,您命令编译器调用名称空间 std 中独一无二的 cout。

// Pre-processor directive
#include <iostream>

// Start of your program
int main()
{
   // Tell the compiler what namespace to look in
   using namespace std;

   /* Write to the screen using cout */
   cout << "Hello World" << endl;

   // Return a value to the OS
   return 0;
}

使用函数之前需要声明

#include <iostream>
using namespace std;

// Function declaration
int DemoConsoleOutput();

int main()
{
   // Function call
    DemoConsoleOutput();

    return 0;
}

// Function definition
int DemoConsoleOutput()
{
    cout << "This is a simple string literal" << endl;
    cout << "Writing number five: " << 5 << endl;
    cout << "Performing division 10 / 5 = " << 10 / 5 << endl;
    cout << "Pi when approximated is 22 / 7 = " << 22 / 7 << endl;
    cout << "Pi actually is 22 / 7 = " << 22.0 / 7 << endl;

    return 0;
}

使用变量最好初始化

int firstNumber = 0;

除非给变量赋初值,否则无法确保相应内存单元的内容是什么,这对程序可能不利。因此,初始化虽然是可选的,但这样做通常是一个不错的编程习惯。

常见变量类型

基本类型(基本数据类型)

‌整型(Integer types)

int:标准整型,通常是32位。 short:短整型,通常是16位。 long:长整型,通常是32位或64位,取决于系统。 long long:更长整型,通常是64位。 unsigned:无符号整型,可以是unsigned int、unsigned short等。

‌字符型(Character types)

char:字符类型,通常是8位。 signed char:有符号字符类型。 unsigned char:无符号字符类型。

‌浮点型(Floating-point types)

float:单精度浮点型,通常是32位。 double:双精度浮点型,通常是64位。 long double:扩展精度浮点型,通常是80位或更高。

‌布尔型(Boolean type)‌

bool:布尔类型,可以存储true或false。

auto可以自动推断类型

#include <iostream>
using namespace std;

int main()
{
   auto coinFlippedHeads = true;
   auto largeNumber = 2500000000000;

   cout << "coinFlippedHeads = " << coinFlippedHeads;
   cout << " , sizeof(coinFlippedHeads) = " << sizeof(coinFlippedHeads) << endl;
   cout << "largeNumber = " << largeNumber;
   cout << " , sizeof(largeNumber) = " << sizeof(largeNumber) << endl;

   return 0;
}

/**
coinFlippedHeads = 1 , sizeof(coinFlippedHeads) = 1
largeNumber = 2500000000000 , sizeof(largeNumber) = 8 
*/

typedef更改变量类型别名

C++允许您将变量类型替换为您认为方便的名称,为此可使用关键字 typedef。

在下面的示例中,程序员想给 unsigned int 指定一个更具描述性的名称— STRICTLY_POSITIVE_INTEGER

typedef unsigned int STRICTLY_POSITIVE_INTEGER;
STRICTLY_POSITIVE_INTEGER numEggsInBasket = 4532;

常量const和constexpr

如果变量的值不应改变,就应将其声明为常量,这是一种良好的编程习惯。通过使用关键字 const,程序员可确保数据不变,避免应用程序无意间修改该常量。在多位程序员合作开发时,这特别有用。

通过constexpr将函数声明为返回常量的函数:

constexpr double GetPi() {return 22.0 / 7;}

会在编译期就算出这个值,并在使用处自动替换,可以优化性能。

但是像计算用户输入数字的两倍,这种地方,就无法计算结果,不保证可以优化性能。

枚举

后一个都比前一个大1。默认第一个数值从0开始,中间也可以自己指定。

#include <iostream>
using namespace std;

enum CardinalDirections
{
   North = 25,
   South,
   East,
   West
};

int main()
{
    cout << "Displaying directions and their symbolic values" << endl;
    cout << "North: " << North << endl;
    cout << "South: " << South << endl;
    cout << "East: " << East << endl;
    cout << "West: " << West << endl;

   CardinalDirections windDirection = South;
   cout << "Variable windDirection = " << windDirection << endl;

   return 0;
}

#define pi 3.14286用来定义常量,已废弃

数组声明和访问元素

#include <iostream>

using namespace std;

int main ()
{
   int myNumbers [5] = {34, 56, -21, 5002, 365};

   cout << "First element at index 0: " << myNumbers [0] << endl;
   cout << "Second element at index 1: " << myNumbers [1] << endl;
   cout << "Third element at index 2: " << myNumbers [2] << endl;
   cout << "Fourth element at index 3: " << myNumbers [3] << endl;
   cout << "Fifth element at index 4: " << myNumbers [4] << endl;

   return 0;
}

多维数组

#include <iostream>
using namespace std;

int main()
{
   int threeRowsThreeColumns [3][3] = { {-501, 205, 2011}, {989, 101, 206}, {303, 456, 596} };
   
   cout << "Row 0: " << threeRowsThreeColumns [0][0] << " " << threeRowsThreeColumns [0][1] << " " << threeRowsThreeColumns [0][2] << endl;

   cout << "Row 1: " << threeRowsThreeColumns [1][0] << " " << threeRowsThreeColumns [1][1] << " " << threeRowsThreeColumns [1][2] << endl;

   cout << "Row 2: " << threeRowsThreeColumns [2][0] << " " << threeRowsThreeColumns [2][1] << " " << threeRowsThreeColumns [2][2] << endl;

   return 0;
}

使用vector声明动态数组

#include <iostream>
#include <vector>

using namespace std;

int main()
{
   vector<int> dynArray (3);

   dynArray[0] = 365;
   dynArray[1] = -421;
   dynArray[2]= 789;

   cout << "Number of integers in array: " << dynArray.size() << endl;

   cout << "Enter another element to insert" << endl;
   int newValue = 0;
   cin >> newValue;
   dynArray.push_back(newValue);

   cout << "Number of integers in array: " << dynArray.size() << endl;
   cout << "Last element in array: ";
   cout << dynArray[dynArray.size() - 1] << endl;
   
   return 0;
}

c风格字符串

char sayHello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd',
'\0'};
std::cout << sayHello << std::endl;

末尾是一个 \0 ,来告诉编译器字符串到这里就完结了。

危险性

使用 C 语言编写的应用程序经常使用 strcpy() 等字符串复制函数、strcat() 等拼接函数,还经常使用 strlen() 来确定字符串的长度;具有 C 语言背景的 C++程序员编写的应用程序亦如此。

这些 C 风格字符串作为输入的函数非常危险,因为它们寻找终止空字符,如果程序员没有在字符数组末尾添加空字符,这些函数将跨越字符数组的边界。

C++ string

要使用 C++字符串,需要包含头文件 string, #include <string> 不同于字符数组(C 风格字符串实现),std::string 是动态的,在需要存储更多数据时其容量将增大。

#include <iostream>
#include <string>

using namespace std;

int main()
{
   string greetStrings ("Hello std::string!");
   cout << greetStrings << endl;

   cout << "Enter a line of text: " << endl;
   string firstLine;
   getline(cin, firstLine);

   cout << "Enter another: " << endl;
   string secondLine;
   getline(cin, secondLine);

   cout << "Result of concatenation: " << endl;
   string concatString = firstLine + " " + secondLine;
   cout << concatString << endl;

   cout << "Copy of concatenated string: " << endl;
   string aCopy;
   aCopy = concatString;
   cout << aCopy << endl;

   cout << "Length of concat string: " << concatString.length() << endl;

   return 0;
}

函数值传递

默认情况下, 函数参数在作用域生效的是外部实参的拷贝 ,内部的操作不会影响原参数。

可以使用按引用传递的参数,在函数体内部也可以对外部传来的参数做修改。

#include <iostream>
using namespace std;

const double Pi = 3.1416;

// output parameter result by reference 
void Area(double radius, double& result)
{
   result = Pi * radius * radius;
}

int main() 
{
   cout << "Enter radius: ";
   double radius = 0;
   cin >> radius;

   double areaFetched = 0;
   Area(radius, areaFetched);

   cout << "The area is: " << areaFetched << endl;
   return 0;
}

函数调用栈的概念

栈是一种后进先出的内存结构,很像堆叠在一起的盘子,您从顶部取盘子,这个盘子是最后堆叠上去的。将数据加入栈被称为压入操作;从栈中取出数据被称为弹出操作。栈增大时,栈指针将不断递增,始终指向栈顶。

栈的性质使其非常适合用于处理函数调用。函数被调用时,所有局部变量都在栈中实例化,即被压入栈中。函数执行完毕时,这些局部变量都从栈中弹出,栈指针返回到原来的地方。

inline函数

也叫内联函数,函数执行时间入栈出栈时间 相当的简单函数,可以使用inline关键字,编译器会直接将其展开到调用处,省去入栈出栈的时间。

现代编译器甚至会自动寻找内联机会,帮助合理优化性能。

auto用于函数,同样可以自动推断返回值

指针初始化,以免自动指向垃圾值

与大多数变量一样,除非对指针进行初始化,否则它包含的值将是随机的。

您不希望访问随机的内存地址,因此将指针初始化为 NULL

NULL 是一个可以检查的值,且不会是内存地址。

指针语法

声明一个指针类型,使用 指向的变量类型加上星号(*) ,后跟指针名称。

int* pointsToInt;

使用取地址运算符(&)获取变量的内存地址,取地址运算符是编程中用于获取变量在内存中的地址的符号,通常用符号&表示。它在支持指针操作的编程语言中广泛使用,功能是返回变量的内存地址,以便后续操作。

#include <iostream>
using namespace std;

int main()
{
   int age = 30;

   int* pointsToInt = &age;
   cout << "pointsToInt points to age now" << endl;

   // Displaying the value of pointer
   cout << "pointsToInt = 0x" << hex << pointsToInt << endl;

   int DogsAge = 9;
   pointsToInt = &DogsAge;
   cout << "pointsToInt points to DogsAge now" << endl;

   cout << "pointsToInt = 0x" << hex << pointsToInt << endl;

   return 0;
}

使用解引用运算符(*)获取指针指向的数据值

解引用运算符是编程中用于访问指针所指向内存地址存储值的符号,通常用星号(*)表示‌。它在支持指针操作的编程语言如C语言和C++中广泛使用,主要功能是通过指针间接访问和修改数据。

其核心工作原理是:当对指针使用解引用运算符(如ptr)时,会获取该指针指向内存位置的实际值。例如,若指针int ptr指向整数变量num的地址,则*ptr操作等同于直接操作num的值。

与取地址运算符(&)形成互补关系:取地址运算符用于获取变量的内存地址(如&num返回地址),而解引用运算符则通过地址反向获取值。在面向对象编程中,解引用运算符还用于通过指针访问对象的成员,例如obj->member等价于(*obj).member,其中->被称为结构解引用运算符,是解引用与成员访问的语法糖。

使用时的注意事项包括:

  • ‌空指针解引用风险‌:若指针未初始化或指向无效内存(如空指针),解引用会导致程序崩溃或未定义行为;
  • ‌类型匹配‌:解引用指针类型需与目标数据类型一致,否则可能引发内存错误;
  • ‌运算符优先级‌:解引用运算符(*)的优先级高于自增(++),因此*p++会先解引用再对指针自增,而(*p)++则是对解引用的值自增。

典型应用场景包括动态内存管理、数据结构(如链表)操作及数组遍历。例如,通过指针遍历数组时,*(arr + i)等效于arr[i],这展示了指针算术与解引用的结合。

#include <iostream>
using namespace std;

int main()
{
   int age = 30;
   int dogsAge = 9;

   cout << "Integer age = " << age << endl;
   cout << "Integer dogsAge = " << dogsAge << endl;

   int* pointsToInt = &age;
   cout << "pointsToInt points to age" << endl;

   // Displaying the value of pointer
   cout << "pointsToInt = 0x" << hex << pointsToInt << endl;

   // Displaying the value at the pointed location
   cout << "*pointsToInt = " << dec << *pointsToInt << endl;

   pointsToInt = &dogsAge;
   cout << "pointsToInt points to dogsAge now" << endl;

   cout << "pointsToInt = 0x" << hex << pointsToInt << endl;
   cout << "*pointsToInt = " << dec << *pointsToInt << endl;

   return 0;
}

如果指针未初始化,它所在的内存单元将包含随机值,此时对其解除引用通常会导致非法访问(Access Violation),即访问应用程序未获得授权的内存单元。

使用new和delete动态的申请和释放内存

这两个必须成对使用。

不再使用分配的内存后,如果不释放它们,这些内存仍被预留并分配给您的应用程序。这将减少可供其他应用程序使用的系统内存量,甚至降低您的应用程序的执行速度。这被称为 内存泄露 ,应不惜一切代价避免这种情况发生。

#include <iostream>
using namespace std;

int main()
{
   // Request for memory space for an int
   int* pointsToAnAge = new int;

   // Use the allocated memory to store a number
   cout << "Enter your dog's age: ";
   cin >> *pointsToAnAge;

   // use indirection operator* to access value 
   cout << "Age " << *pointsToAnAge << " is stored at 0x" << hex << pointsToAnAge << endl;

   delete pointsToAnAge; // release dynamically allocated memory

   return 0;
}

不能将运算符 delete 用于任何包含地址的指针,而只能用于 new 返回的且未使用 delete 释放的指针。

对指针使用++和–

其会指向下一个int值,而不是移动一个内存地址指向中间,那毫无意义。 如果声明了如下指针:

Type* pType = Address;

则执行 ++pType 后, pType 将指向 Address + sizeof(Type)

可以推断,数组其实就是一个指向第一个元素的指针类型。

数组和指针

由于数组变量就是指针,因此也可将用于指针的解除引用运算符(*)用于数组。同样,可将数组运算符[ ]用于指针。

#include <iostream>
using namespace std;

int main()
{
   const int ARRAY_LEN = 5;

   // Static array of 5 integers, initialized
   int myNumbers[ARRAY_LEN] = {24, -1, 365, -999, 2011};

   // Pointer initialized to first element in array
   int* pointToNums = myNumbers;

   cout << "Display array using pointer syntax, operator*" << endl;
   for (int index = 0; index < ARRAY_LEN; ++index)
      cout << "Element " << index << " = " << *(myNumbers + index) << endl;

   cout << "Display array using ptr with array syntax, operator[]" << endl;
   for (int index = 0; index < ARRAY_LEN; ++index)
      cout << "Element " << index << " = " << pointToNums[index] << endl;

   return 0;
}

const指针

指针的主要功能是指向一个 变量的地址 ,理解为越靠近变量的限制性越高级。

  • const贴近变量名,不允许修改指针指向的地址,可以修改指向的变量。
#include <iostream>
using namespace std;

int main()  
{
   int age = 30;
   int dogsAge = 9;

   int* const pointsToAge = &age;
   cout << "*pointsToAge = " << *pointsToAge << endl;

   // pointsToAge = &dogsAge; // error! can't change pointer value
   *pointsToAge = 31; // ok! can change value pointed to
   cout << "*pointsToAge = " << *pointsToAge << endl;

   return 0;
}
  • const远离变量名,在最前,不允许修改指向变量的值,指针可以指向其他地方。
#include <iostream>
using namespace std;

int main()
{
   int age = 30;
   int dogsAge = 9;

   const int* pointsToAge = &age;
   cout << "*pointsToAge = " << *pointsToAge << endl;

   // *pointsToAge = 31; // error! can't change value pointed to
   pointsToAge = &dogsAge; // ok! can change pointer value
   cout << "*pointsToAge = " << *pointsToAge << endl;

   return 0; 
}
  • 两个都有 const 修饰,则都不可以修改。
#include <iostream>
using namespace std;

int main()
{
   int age = 30;
   int dogsAge = 9;

   const int* const pointsToAge = &age;
   cout << "*pointsToAge = " << *pointsToAge << endl;

   // *pointsToAge = 31; // error! can't change value pointed to
   // pointsToAge = &dogsAge; // error! can't change pointer value

   return 0; 
}

指针传递给函数

需要提前规定好 函数内部可以修改哪些值,不可以修改哪些值

#include <iostream>
using namespace std;

void CalcArea(const double* const ptrPi, // const pointer to const data
              const double* const ptrRadius, // i.e. no changes allowed
              double* const ptrArea)  // can change data pointed to,but not pointer
{
   // check pointers for validity before using!
   if (ptrPi && ptrRadius && ptrArea) 
      *ptrArea = (*ptrPi) * (*ptrRadius) * (*ptrRadius);
}

int main()
{
   const double Pi = 3.1416;

   cout << "Enter radius of circle: ";
   double radius = 0;
   cin >> radius;

   double area = 0;
   CalcArea (&Pi, &radius, &area);

   cout << "Area is = " << area << endl;

   return 0;
}

使用new的内存分配请求可能失败

为防止报错,此时使用 try-catch 或者使用 new(nothrow) ,它在分配内存失败时返回 NULL 。也不会报错退出。

  • try catch
#include <iostream>
using namespace std;

// remove the try-catch block to see this application crash 
int main()
{
   try
   {
      // Request a LOT of memory!
      int* pointsToManyNums = new int [0x1fffffff];

      // Use the allocated memory 

      delete[] pointsToManyNums;
   }
   catch (bad_alloc)
   {
      cout << "Memory allocation failed. Ending program" << endl;
   }
   return 0;
}
  • 使用new(nothrow)
#include <iostream>
using namespace std;

int main()
{
   // Request LOTS of memory space, use nothrow 
   int* pointsToManyNums = new(nothrow) int [0x1fffffff];

   if (pointsToManyNums) // check pointsToManyNums != NULL
   {
      // Use the allocated memory 
      delete[] pointsToManyNums;
   }
   else 
      cout << "Memory allocation failed. Ending program" << endl;

   return 0;
}

引用&是变量的别名

引用是变量的别名。声明引用时,需要将其初始化为一个变量,因此引用只是另一种访问相应变量存储的数据的方式。

#include <iostream>
using namespace std;

int main()
{
   int original = 30;
   cout << "original = " << original << endl;
   cout << "original is at address: " << hex << &original << endl;

   int& ref1 = original;
   cout << "ref1 is at address: " << hex << &ref1 << endl;

   int& ref2 = ref1;
   cout << "ref2 is at address: " << hex << &ref2 << endl;
   cout << "Therefore, ref2 = " << dec << ref2 << endl;

   return 0;
}

他们指向同一个地址。

引用的用处

函数参数,如果在合适的时机,按引用传递,可以省去变量复制的步骤,优化性能。

#include <iostream>
using namespace std;

void GetSquare(int& number)
{
   number *= number;
}

int main()
{
   cout << "Enter a number you wish to square: ";
   int number = 0;
   cin >> number;

   GetSquare(number);
   cout << "Square is: " << number << endl;

   return 0;
}

const用于引用

通过引用可以修改变量的值。可能需要禁止通过引用修改它指向的变量的值,为此可在声明引用时使用关键字 const。

int original = 30;
const int& constRef = original;
constRef = 40; // Not allowed: constRef can’t change value in original
int& ref2 = constRef; // Not allowed: ref2 is not const
const int& constRef2 = constRef; // OK

结合上一个,传参时,使用const引用,又可以避免复制,又可以确保函数体中,不可以修改按引用传进去的变量的值

声明和使用类

#include <iostream>
#include <string>
using namespace std;

class Human
{
public:
   string name;
   int age;

   void IntroduceSelf()
   {
      cout << "I am " + name << " and am ";
      cout << age << " years old" << endl;
   }
};

int main()
{
   // An object of class Human with attribute name as "Adam"
   Human firstMan;
   firstMan.name = "Adam";
   firstMan.age = 30;

   // An object of class Human with attribute name as "Eve"
   Human firstWoman;
   firstWoman.name = "Eve";
   firstWoman.age = 28;
   
   firstMan.IntroduceSelf();
   firstWoman.IntroduceSelf();
}

使用指针运算符访问成员

如果对象是使用 new 在自由存储区中实例化的,或者有指向对象的指针,则可使用指针运算符 -> 来访问成员属性和方法:

Human* firstWoman = new Human();
firstWoman->dateOfBirth = "1970";
firstWoman->IntroduceSelf();
delete firstWoman;

类中的变量和函数,如果未标明,默认都为 private ,外部不可以访问 默认构造函数:不用传参的构造函数,并非仅指无参构造函数。

构造函数可以重载,也可以设置成必须要初始化参数,还可以带默认参数 带初始化列表的构造函数。

写法如下:

#include <iostream>
#include <string>
using namespace std;

class Human
{
private:
   int age;
   string name;

public:
   Human(string humansName = "Adam", int humansAge = 25)
        :name(humansName), age(humansAge)
   {
      cout << "Constructed a human called " << name;
      cout << ", " << age << " years old" << endl;
   }
};

int main()
{
   Human adam;
   Human eve("Eve", 18);

   return 0;
}

析构函数

用于回收资源,当类中有动态申请内存的操作时,一般需要设计析构函数,在类销毁时释放内存,声明为 ~ClassName() {}

#include <iostream>
#include <string.h>
using namespace std;

class MyString
{
private:
   char* buffer;

public:
   MyString(const char* initString)  // constructor
   {
      if(initString != NULL)
      {
         buffer = new char [strlen(initString) + 1];
         strcpy(buffer, initString);
      }
      else 
         buffer = NULL;
   }

   ~MyString()
   {
      cout << "Invoking destructor, clearing up" << endl;
      if (buffer != NULL)
         delete [] buffer;
   }

   int GetLength() 
   {
      return strlen(buffer);
   }

   const char* GetString()
   {
       return buffer;
   }
};

int main()
{
   MyString sayHello("Hello from String Class");
   cout << "String buffer in sayHello is " << sayHello.GetLength();
   cout << " characters long" << endl;

   cout << "Buffer contains: " << sayHello.GetString() << endl;
}

类的浅复制的问题

把类当作参数传递给函数时,使用值传递模式,其会被复制。

如果其内部有指针指向的new出来的缓冲区,复制时会复制指针成员,但是不会复制一份缓冲区,在函数结束时,回收这个复制出来的类对象,删除掉了缓冲区,导致外部的类指向无效的内存地址。在外部类生命周期完结时,delete无效内存会报错。

#include <iostream>
#include <string.h>
using namespace std;

class MyString
{
private:
   char* buffer;

public:
   MyString(const char* initString) // Constructor
   {
      buffer = NULL;
      if(initString != NULL)
      {
         buffer = new char [strlen(initString) + 1];
         strcpy(buffer, initString);
      }
   }

   ~MyString() // Destructor
   {
      cout << "Invoking destructor, clearing up" << endl;
      delete [] buffer;
   }

   int GetLength() 
   { return strlen(buffer); }

   const char* GetString()
   { return buffer; }
};

void UseMyString(MyString str)
{
   cout << "String buffer in MyString is " << str.GetLength();
   cout << " characters long" << endl;

   cout << "buffer contains: " << str.GetString() << endl;
   return;
}

int main()
{
   MyString sayHello("Hello from String Class");
   UseMyString(sayHello); 

   return 0;
}

/**
String buffer in MyString is 23 characters long
buffer contains: Hello from String Class
Invoking destructor, clearing up
Invoking destructor, clearing up
<crash as seen in Figure 9.2>
*/

复制构造函数

一个专门的用于复制流程的构造函数,当通过 = 传递来复制类,或者当作函数参数来复制时。编译器会自动调用这个构造函数来生成一个新的对象。

默认格式为,传入一个 const引用 的构造函数,借用这个外部对象的数据来重新构造一个新的复制对象。

#include <iostream>
#include <string.h>
using namespace std;

class MyString
{
private:
   char* buffer;

public:
   MyString() {}
   MyString(const char* initString) // constructor
   {
      buffer = NULL;
      cout << "Default constructor: creating new MyString" << endl;
      if(initString != NULL)
      {
         buffer = new char [strlen(initString) + 1];
         strcpy(buffer, initString);

         cout << "buffer points to: 0x" << hex;
         cout << (unsigned int*)buffer << endl;
      }
   }

   MyString(const MyString& copySource) // Copy constructor
   {
      buffer = NULL;
      cout << "Copy constructor: copying from MyString" << endl;
      if(copySource.buffer != NULL)
      {
         // allocate own buffer 
         buffer = new char [strlen(copySource.buffer) + 1];

         // deep copy from the source into local buffer
         strcpy(buffer, copySource.buffer);

         cout << "buffer points to: 0x" << hex;
         cout << (unsigned int*)buffer << endl;
      }
   }

   MyString operator+ (const MyString& addThis) 
   {
      MyString newString;

      if (addThis.buffer != NULL)
      {
         newString.buffer = new char[GetLength() + strlen(addThis.buffer) + 1];
         strcpy(newString.buffer, buffer);
         strcat(newString.buffer, addThis.buffer);
      }

      return newString;
   }

   // Destructor
   ~MyString()
   {
      cout << "Invoking destructor, clearing up" << endl;
      delete [] buffer;
   }

   int GetLength() 
   { return strlen(buffer); }

   const char* GetString()
   { return buffer; }
};

void UseMyString(MyString str)
{
   cout << "String buffer in MyString is " << str.GetLength();
   cout << " characters long" << endl;

   cout << "buffer contains: " << str.GetString() << endl;
   return;
}

int main()
{
   MyString sayHello("Hello from String Class");
   UseMyString(sayHello);

   return 0;
}

将复制构造函数和 = 运算符覆写成私有的

这个类不允许复制操作,编译时就会提示。

单例类

进一步将构造函数设置私有,提供一个static函数,返回一个static对象的引用,就是一个单例类,禁止复制,赋值,创建多实例。static对象只会创建一次,全局均可访问。所有的地方调用的都是这同一个实例。

#include <iostream>
#include <string>
using namespace std;

class President
{
private:
   President() {}; // private default constructor
   President(const President&); // private copy constructor
   const President& operator=(const President&); // assignment operator

   string name;

public:
   static President& GetInstance()
   {
      // static objects are constructed only once
      static President onlyInstance; 
      return onlyInstance;
   }

   string GetName()
   { return name; }

   void SetName(string InputName)
   { name = InputName; }
};

int main()
{
   President& onlyPresident = President::GetInstance();
   onlyPresident.SetName("Abraham Lincoln");

   // uncomment lines to see how compile failures prohibit duplicates
   // President second; // cannot access constructor
   // President* third= new President(); // cannot access constructor
   // President fourth = onlyPresident; // cannot access copy constructor
   // onlyPresident = President::GetInstance(); // cannot access operator=

   cout << "The name of the President is: ";
   cout << President::GetInstance().GetName() << endl;

   return 0;
}

将析构函数设为私有,就禁止在栈中实例化

只能通过 new 关键字,在自由存储区实例化

#include <iostream>
using namespace std;

class MonsterDB 
{
private:
   ~MonsterDB() {}; // private destructor prevents instances on stack

public:
   static void DestroyInstance(MonsterDB* pInstance)
   {
      delete pInstance; // member can invoke private destructor
   }

   void DoSomething() {} // sample member method
};

int main()
{
   MonsterDB* myDB = new MonsterDB(); // on heap
   myDB->DoSomething();

   // uncomment next line to see compile failure 
   // delete myDB; // private destructor cannot be invoked

   // use static member to release memory
   MonsterDB::DestroyInstance(myDB);

   return 0;
}

隐式转换和预防

Human类构造函数接受一个int类型作为参数。

这样的转换构造函数让您能够执行隐式转换:

Human anotherKid = 11; // int converted to Human
DoSomething(10); // 10 converted to Human!

函数 DoSomething(Human person)被声明为接受一个 Human(而不是 int)参数!前面的代码为何可行呢?这是因为编译器知道 Human 类包含一个将整数作为参数的构造函数,进而替您执行了隐式转换:将您提供的整数作为参数发送给这个构造函数,从而创建一个Human 对象。

使用 explicit 关键字避免隐式转换:

#include<iostream>
using namespace std;

class Human
{
   int age;
public:
   // explicit constructor blocks implicit conversions
   explicit Human(int humansAge) : age(humansAge) {}
};

void DoSomething(Human person)
{
   cout << "Human sent did something" << endl;
   return;
}

int main()
{
   Human kid(10);    // explicit converion is OK
   Human anotherKid = Human(11); // explicit, OK
   DoSomething(kid); // OK

   // Human anotherKid = 11; // failure: implicit conversion not OK
   // DoSomething(10); // implicit conversion 

   return 0;
}

this指针

当您在类成员方法中调用其他成员方法时,编译器将隐式地传递 this 指针—函数调用中不可见的参数:

class Human
{
private:
 void Talk (string Statement)
 {
 cout << Statement;
 }
public:
 void IntroduceSelf()
 {
 Talk("Bla bla"); // same as Talk(this, "Bla Bla")
 }
};

在这里,方法 IntroduceSelf( )使用私有成员 Talk( )在屏幕上显示一句话。实际上,编译器将在调用Talk 时嵌入 this 指针,即:

Talk(this, Blab la)

sizeof()用于类

在这种情况下,它将指出类声明中所有数据属性占用的总内存量,单位为字节。 sizeof() 可能对某些属性进行填充,使其与字边界对齐,也可能不这样做,这取决于您使用的编译器。

友元类和友元函数

可以访问类的私有private的属性和方法

#include <iostream>
#include <string>
using namespace std;

class Human
{
private:
   friend void DisplayAge(const Human& person);
   string name;
   int age;

public:
   Human(string humansName, int humansAge) 
   {
      name = humansName;
      age = humansAge;
   }
};

void DisplayAge(const Human& person)
{
   cout << person.age << endl;
}

int main()
{
   Human firstMan("Adam", 25);
   cout << "Accessing private member age via friend function: ";
   DisplayAge(firstMan);

   return 0;
}#include <iostream>
#include <string>
using namespace std;

class Human
{
private:
   friend class Utility;
   string name;
   int age;

public:
   Human(string humansName, int humansAge) 
   {
      name = humansName;
      age = humansAge;
   }
};

class Utility
{
public:
   static void DisplayAge(const Human& person)
   {
      cout << person.age << endl;
   }
};

int main()
{
   Human firstMan("Adam", 25);
   cout << "Accessing private member age via friend class: ";
   Utility::DisplayAge(firstMan);

   return 0;
}

盲猜用于共同实现某一功能的两个模块,比如混动车的燃油发动机给电动机供电。

struct结构体,和类类似,属性默认为公开

关键字 struct 来自 C 语言,在 C++编译器看来,它与类及其相似,差别在于程序员未指定时,默认的访问限定符(public 和 private)不同。因此,除非指定了,否则结构中的成员默认为公有的(而类成员默认为私有的);另外,除非指定了,否则结构以公有方式继承基结构(而类为私有继承)。

union共用体

共用体是一种特殊的类,每次只有一个非静态数据成员处于活动状态。因此,共用体与类一样,可包含多个数据成员,但不同的是只能使用其中的一个。

与结构类似,共用体的成员默认也是公有的,但不同的是,共用体不能继承。另外,将 sizeof() 用于共用体时,结果总是为共用体最大成员的长度,即便该成员并不处于活动状态。

常见使用场景

#include <iostream>
using namespace std;

union SimpleUnion
{
   int num;
   char alphabet;
};

struct ComplexType
{
   enum DataType
   {
      Int,
      Char
   } Type;

   union Value
   {
      int num;
      char alphabet;

      Value() {}
      ~Value() {}
   }value;
};

void DisplayComplexType(const ComplexType& obj)
{
   switch (obj.Type)
   {
   case ComplexType::Int:
      cout << "Union contains number: " << obj.value.num << endl;
      break;

   case ComplexType::Char:
      cout << "Union contains character: " << obj.value.alphabet << endl;
      break;
   }
}

int main()
{
   SimpleUnion u1, u2;
   u1.num = 2100;
   u2.alphabet = 'C';

   // Alternative using aggregate initialization:
   // SimpleUnion u1{ 2100 }, u2{ 'C' }; // Note that 'C' still initializes first / int member

   cout << "sizeof(u1) containing integer: " << sizeof(u1) << endl;
   cout << "sizeof(u2) containing character: " << sizeof(u2) << endl;

   ComplexType myData1, myData2;
   myData1.Type = ComplexType::Int;
   myData1.value.num = 2017;

   myData2.Type = ComplexType::Char;
   myData2.value.alphabet = 'X';

   DisplayComplexType(myData1);
   DisplayComplexType(myData2);

   return 0;
}

/**
sizeof(u1) containing integer: 4
sizeof(u2) containing character: 4
Union contains number: 2017
Union contains character: X
*/

换句话说,这个结构使用枚举来存储信息类型,并使用共用体来存储实际值。这是共用体的一种常见用法,例如,在 Windows 应用程序编程中常用的结构 VARIANT 就以这样的方式使用了共用体.

聚合初始化

#include <iostream>
#include<string>
using namespace std;

class Aggregate1
{
public:
   int num;
   double pi;
};

struct Aggregate2
{
   char hello[6];
   int impYears[3];
   string world;
};

int main()
{
   int myNums[] = { 9, 5, -1 }; // myNums is int[3]
   Aggregate1 a1{ 2017, 3.14 };
   cout << "Pi is approximately: " << a1.pi << endl;

   Aggregate2 a2{ {'h', 'e', 'l', 'l', 'o'}, {2011, 2014, 2017}, "world"};

   // Alternatively
   Aggregate2 a2_2{'h', 'e', 'l', 'l', 'o', '\0', 2011, 2014, 2017, "world"};

   cout << a2.hello << ' ' << a2.world << endl;
   cout << "C++ standard update scheduled in: " << a2.impYears[2] << endl;

   return 0;
}

constexpr还可以用于类的构造函数和成员函数,编译器会尽可能将其视为常量处理

#include <iostream>
using namespace std;

class Human
{
    int age;
public:
    constexpr Human(int humansAge) :age(humansAge) {}
    constexpr int GetAge() const { return age; }
};

int main()
{
    constexpr Human somePerson(15);
    const int hisAge = somePerson.GetAge();

    Human anotherPerson(45); // not constant expression

    return 0;
}

最简单的继承

#include <iostream>
using namespace std; 


class Fish
{
public:
   bool isFreshWaterFish;

   void Swim()
   {
      if (isFreshWaterFish)
         cout << "Swims in lake" << endl;
      else
         cout << "Swims in sea" << endl;
   }
};

class Tuna: public Fish
{
public:
   Tuna()
   {
      isFreshWaterFish = false;
   }
};

class Carp: public Fish
{
public:
   Carp()
   {
      isFreshWaterFish = true;
   }
};

int main()
{
   Carp myLunch;
   Tuna myDinner;

   cout << "Getting my food to swim" << endl;

   cout << "Lunch: ";
   myLunch.Swim();

   cout << "Dinner: ";
   myDinner.Swim();

   return 0;
}

基类使用protected关键字,该成员只有子类和友元中可以访问,外部不可以访问

基类构造器可以带参数,子类构造器必须一起初始化基类的构造器

#include <iostream>
using namespace std; 

class Fish
{
protected:
   bool isFreshWaterFish; // accessible only to derived classes

public:
   // Fish constructor
   Fish(bool isFreshWater) : isFreshWaterFish(isFreshWater){}

   void Swim()
   {
      if (isFreshWaterFish)
         cout << "Swims in lake" << endl;
      else
         cout << "Swims in sea" << endl;
   }
};

class Tuna: public Fish
{
public:
   Tuna(): Fish(false) {}
};

class Carp: public Fish
{
public:
   Carp(): Fish(true) {}
};

int main()
{
   Carp myLunch;
   Tuna myDinner;

   cout << "Getting my food to swim" << endl;

   cout << "Lunch: ";
   myLunch.Swim();

   cout << "Dinner: ";
   myDinner.Swim();

   return 0;
}

基类属性和方法的覆写

如果派生类实现了从基类继承的函数,且返回值和特征标相同,就相当于覆盖了基类的这个方法。

如果基类的方法是public的,外部可以通过域解析运算符::来调用基类方法。 myDinner.Fish::Swim(); // invokes Fish::Swim() using instance of Tuna在子类中,同样用上述方法来调用。

隐藏基类方法

基类中有同名的重载方法时,子类覆写其中一个,子类中会对外隐藏所有的同名方法。

#include <iostream>
using namespace std; 
   
class Fish
{
public:
   void Swim()
   {
       cout << "Fish swims... !" << endl;
   }

   void Swim(bool isFreshWaterFish)
   {
      if (isFreshWaterFish)
         cout << "Swims in lake" << endl;
      else
         cout << "Swims in sea" << endl;
   }
};

class Tuna: public Fish
{
public:
   void Swim(bool isFreshWaterFish)
   {
       Fish::Swim(isFreshWaterFish);
   }

   void Swim()
   {
      cout << "Tuna swims real fast" << endl;
   }
};

int main()
{
   Tuna myDinner;

   cout << "Getting my food to swim" << endl;
   
   myDinner.Swim(false);//failure: Tuna::Swim() hides Fish::Swim(bool)

   myDinner.Swim();

   return 0;
}

要想解除隐藏,外部可以通过域解析运算符直接调用到基类的方法。或者在子类中使用using解除对基类方法的隐藏。

class Tuna: public Fish
{
public:
 using Fish::Swim; // unhide all Swim() methods in class Fish
 void Swim()
 {
 cout << "Tuna swims real fast" << endl;
 }
}; 

构造和析构顺序

构造时先构造基类部分,再构造子类部分。

析构时先调用子类的,再调用基类的。

class Car:private Motor私有继承

只有子类可以访问基类中的属性,方法,外部不可以调用基类方法。也不可以通过域解析运算符访问。

私有继承时,子类的子类同样不可以访问基类的方法。例如RaceCar继承自Car,它也访问不了Motor

protected保护继承

同样屏蔽了外部访问,但是使用保护继承的子类的子类可以访问到基类的属性,方法。

仅当必要时才使用私有或保护继承。

对于大多数使用私有继承的情形(如 Car 和 Motor 之间的私有继承),更好的选择是,将基类对象作为派生类的一个成员属性。通过继承 Motor 类,相当于对 Car 类进行了限制,使其只能有一台发动机,同时,相比于将 Motor 对象作为私有成员,没有任何好处可言。汽车在不断发展,例如,混合动力车除电力发动机外,还有一台汽油发动机。在这种情况下,让 Car 类继承 Motor 类将成为兼容性瓶颈。

class Car
{
private:
 Motor heartOfCar;
public:
 void Move()
 {
 heartOfCar.SwitchIgnition();
 heartOfCar.PumpFuel();
 heartOfCar.FireCylinders();
 }
}; 

切除问题

一个方法接受一个基类参数,但是传递一个子类对象过去,这时候,复制机制只会复制基类部分,子类的部分将被切除。

可以同时继承多个基类

使用final关键字禁止继承,表示其为最终的子类

多态

上面的切除问题可以使用多态的特性来规避。让我们可以用类似的方式处理不同类型的对象。

将基类的方法声明为虚函数,可以确保编译器调用子类中的覆写方法。

下面的例子中,子类中的方法声明为:virtual void Swim()

#include <iostream>
using namespace std;

class Fish
{
public:
   virtual void Swim()
   {
      cout << "Fish swims!" << endl;
   }
};

class Tuna:public Fish
{
public:
   // override Fish::Swim
   void Swim()
   {
      cout << "Tuna swims!" << endl;
   }
};

class Carp:public Fish
{
public:
   // override Fish::Swim
   void Swim()
   {
      cout << "Carp swims!" << endl;
   }
};

void MakeFishSwim(Fish& InputFish)
{
   // calling Swim
   InputFish.Swim();
}

int main() 
{
   Tuna myDinner;
   Carp myLunch;

   // sending Tuna as Fish
   MakeFishSwim(myDinner);

   // sending Carp as Fish
   MakeFishSwim(myLunch);

   return 0;
}

/**
Tuna swims!
Carp swims!
*/

因为存在覆盖版本 Tuna::Swim()Carp::Swim() ,它们优先于被声明为虚函数的 Fish::Swim() 。这很重要,它意味着在 MakeFishSwim() 中,可通过 Fish& 参数调用派生类定义的 Swim() ,而无需知道该参数指向的是哪种类型的对象。

这就是多态:将派生类对象视为基类对象,并执行派生类的 Swim() 实现。

类似的,析构函数也需要声明为虚析构函数

将子类指针当作基类指针传递参数时,函数体内调用删除方法,删除时只会调用基类的析构函数,子类的部分将不会回收,将造成内存泄漏。

将基类的析构函数声明为 vitual 的,再使用基类指针删除时,将确保调用到子类的析构函数。

#include <iostream>
using namespace std;

class Fish
{
public:
   Fish()
   {
      cout << "Constructed Fish" << endl;
   }
   virtual ~Fish()   // virtual destructor!
   {
      cout << "Destroyed Fish" << endl;
   }
};

class Tuna:public Fish
{
public:
   Tuna()
   {
      cout << "Constructed Tuna" << endl;
   }
   ~Tuna()
   {
      cout << "Destroyed Tuna" << endl;
   }
};

void DeleteFishMemory(Fish* pFish)
{
   delete pFish;
}

int main() 
{
   cout << "Allocating a Tuna on the free store:" << endl;
   Tuna* pTuna = new Tuna;
   cout << "Deleting the Tuna: " << endl;
   DeleteFishMemory(pTuna);

   cout << "Instantiating a Tuna on the stack:" << endl;
   Tuna myDinner;
   cout << "Automatic destruction as it goes out of scope: " << endl;

   return 0;
}

虚函数表

编译器将为实现了虚函数的基类和覆盖了虚函数的派生类分别创建一个虚函数表(VirtualFunction Table,VFT)。换句话说,Base 和 Derived 类都将有自己的虚函数表。实例化这些类的对象时,将创建一个隐藏的指针(我们称之为 VFT*),它指向相应的 VFT。可将 VFT 视为一个包含函数指针的静态数组,其中每个指针都指向相应的虚函数。

子类覆写了基类的某些虚函数时,子类的虚函数表的函数指针将指向子类自己的函数实现。

对于未覆写的基类函数,虚函数表中的函数指针会指向基类的函数。

外部调用时,就通过虚函数表来查找到底该调用的子类方法还是基类方法。

有纯虚函数的类可以称为抽象基类

纯虚函数定义:virtual void Swim() = 0;

子类继承了抽象基类,则必须覆写其定义的纯虚函数。

#include <iostream>
using namespace std;

class Fish
{
public:
   // define a pure virtual function Swim
   virtual void Swim() = 0;
};

class Tuna:public Fish
{
public:
   void Swim()
   {
      cout << "Tuna swims fast in the sea!" << endl;
   }
};

class Carp:public Fish
{
   void Swim()
   {
      cout << "Carp swims slow in the lake!" << endl;
   }
};

void MakeFishSwim(Fish& inputFish)
{
   inputFish.Swim();
}

int main()
{
   // Fish myFish;   // Fails, cannot instantiate an ABC
   Carp myLunch;
   Tuna myDinner;

   MakeFishSwim(myLunch);
   MakeFishSwim(myDinner);

   return 0;
}

虚继承解决菱形问题

鸭嘴兽具备哺乳动物、鸟类和爬行动物的特征,这意味着 Platypus 类需要继承Mammal、Bird 和 Reptile。然而,这些类都从同一个类—Animal 派生而来。全部使用常规继承方式,将创建三个animal的实例,甚至可以分别设置每一个的age属性。

当派生类可能用作基类时,使用vitual虚继承是更好的选择。这样当一个类继承多个从相同基类衍生而来的类时,只创建一个基类实例。

#include <iostream>
using namespace std;

class Animal
{
public:
   Animal()
   {
      cout << "Animal constructor" << endl;
   }

   // sample member
   int age;
};

class Mammal:public virtual Animal
{
};

class Bird:public virtual Animal
{
};

class Reptile:public virtual Animal
{
};

class Platypus final:public Mammal, public Bird, public Reptile
{
public:
   Platypus()
   {
      cout << "Platypus constructor" << endl;
   }
};

int main()
{
   Platypus duckBilledP;

   // no compile error as there is only one Animal::age
   duckBilledP.age = 25; 

   return 0;
}

/**
Animal constructor
Platypus constructor
*/

C++关键字 virtual 的含义随上下文而异(我想这样做的目的很可能是为了省事),对其含义总结如下:

  1. 在函数声明中,virtual 意味着当基类指针指向派生对象时,通过它可调用派生类的相应函数。
  2. 从 Base 类派生出 Derived1 和 Derived2 类时,如果使用了关键字 virtual,则意味着再从Derived1 和 Derived2 派生出 Derived3 时,每个 Derived3 实例只包含一个 Base 实例。

也就是说,关键字 virtual 被用于实现两个不同的概念。

override关键字

子类中覆写基类方法时,通过override关键字,检查基类中对应的方法是否声明了虚函数,防止标记错误导致覆写失败。好的编程习惯是每个子类覆写函数后都加入override标记。

虚复制构造函数

不可能实现虚复制构造函数,因为在基类方法声明中使用关键字 virtual 时,表示它将被派生类的实现覆盖,这种多态行为是在运行阶段实现的。而构造函数只能创建固定类型的对象,不具备多态性,因此 C++不允许使用虚复制构造函数。

可以通过自己基类定义虚函数,并在子类实现,一个专门的Clone函数,外部来显式调用,定义返回一个基类指针类型,传入子类指针。就可以让返回的基类指针在调用方法时表现为子类的特性。

#include <iostream>
using namespace std;

class Fish
{
public:
   virtual Fish* Clone() = 0;
   virtual void Swim() = 0;
   virtual ~Fish() {};
};

class Tuna: public Fish
{
public:
   Fish* Clone() override
   {
      return new Tuna (*this);
   }

   void Swim() override final
   {
      cout << "Tuna swims fast in the sea" << endl;
   }
};

class BluefinTuna final:public Tuna
{
public:
   Fish* Clone() override
   {
      return new BluefinTuna(*this);
   }

   // Cannot override Tuna::Swim as it is "final" in Tuna
};

class Carp final: public Fish
{
   Fish* Clone() override
   {
      return new Carp(*this);
   }
   void Swim() override final
   {
      cout << "Carp swims slow in the lake" << endl;
   }
};

int main()
{
   const int ARRAY_SIZE = 4;

   Fish* myFishes[ARRAY_SIZE] = {NULL};
   myFishes[0] = new Tuna();
   myFishes[1] = new Carp();
   myFishes[2] = new BluefinTuna();
   myFishes[3] = new Carp();

   Fish* myNewFishes[ARRAY_SIZE];
   for (int index = 0; index < ARRAY_SIZE; ++index)
      myNewFishes[index] = myFishes[index]->Clone();

   // invoke a virtual method to check
   for (int index = 0; index < ARRAY_SIZE; ++index)
      myNewFishes[index]->Swim();

   // memory cleanup
   for (int index = 0; index < ARRAY_SIZE; ++index)
   {
      delete myFishes[index];
      delete myNewFishes[index];
   }

   return 0;
}

对类使用单目运算符

在类中定义:

return_type operator operator_symbol (...parameter list...);

比如将Date类实现++运算操作。

// also contains postfix increment and decrement

#include <iostream>
using namespace std;

class Date
{
private:
   int day, month, year;

public:
   Date (int inMonth, int inDay, int inYear)
        : month (inMonth), day(inDay), year (inYear) {};

   Date& operator ++ () // prefix increment
   {
      ++day;
      return *this;
   }

   Date& operator -- () // prefix decrement
   {
      --day;
      return *this;
   }

   Date operator ++ (int) // postfix increment
   {
      Date copy(month, day, year);
      ++day;
      return copy;
   }

   Date operator -- (int) // postfix decrement
   {
      Date copy(month, day, year);
      --day;
      return copy;
   }

   void DisplayDate()
   {
      cout << month << " / " << day << " / " << year << endl;
   }
};

int main ()
{
   Date holiday (12, 25, 2016); // Dec 25, 2016

   cout << "The date object is initialized to: ";
   holiday.DisplayDate ();

   ++holiday; // move date ahead by a day
   cout << "Date after prefix-increment is: ";
   holiday.DisplayDate ();

   --holiday; // move date backwards by a day
   cout << "Date after a prefix-decrement is: ";
   holiday.DisplayDate ();

   return 0;
}

转换运算符

operator const char*()
{
 // operator implementation that returns a char*
}

在外部希望这个类以const char*的类型使用时,比如cout « date。可以类比java里的toString。

#include <iostream>
#include <sstream> // new include for ostringstream
#include <string>
using namespace std;

class Date
{
private:
   int day, month, year;
   string dateInString;

public:
   Date(int inMonth, int inDay, int inYear)
      : month(inMonth), day(inDay), year(inYear) {};

   operator const char*()
   {
      ostringstream formattedDate; // assists easy string construction
      formattedDate << month << " / " << day << " / " << year;
     
      dateInString = formattedDate.str();
      return dateInString.c_str();
   }
};
   
int main ()
{
   Date Holiday (12, 25, 2016);

   cout << "Holiday is on: " << Holiday << endl;

   // string strHoliday (Holiday); // OK!
   // strHoliday = Date(11, 11, 2016); // also OK!

   return 0;
}

智能指针初体验

#include <iostream>
#include <memory>  // new include to use unique_ptr
using namespace std;

class Date
{
private:
   int day, month, year;
   string dateInString;   

public:
   Date(int inMonth, int inDay, int inYear)
      : month(inMonth), day(inDay), year(inYear) {};

   void DisplayDate()
   {
      cout << month << " / " << day << " / " << year << endl;
   }
};

int main()
{
   unique_ptr<int> smartIntPtr(new int);
   *smartIntPtr = 42;

   // Use smart pointer type like an int*
   cout << "Integer value is: " << *smartIntPtr << endl;

   unique_ptr<Date> smartHoliday (new Date(12, 25, 2016));
   cout << "The new instance of date contains: ";

   // use smartHoliday just as you would a Date*
   smartHoliday->DisplayDate();

   return 0;
}

这个示例表明,可像使用普通指针那样使用智能指针,如第 23 和 32 行所示。第 23 行使用了smartIntPtr 来显示指向的 int 值,而第 32 行使用了 smartHoliday->DisplayData(),就像这两个变量的类型分别是 int和 Date。其中的秘诀在于,智能指针类 std::unique_ptr 实现了运算符和->

类实现双目加减法

#include <iostream>
using namespace std;
   
class Date
{
private:
   int day, month, year;
   string dateInString;

public:
   Date(int inMonth, int inDay, int inYear)
      : month(inMonth), day(inDay), year(inYear) {};
   
   Date operator + (int daysToAdd) // binary addition
   {
      Date newDate (month, day + daysToAdd, year);
      return newDate;
   }

   Date operator - (int daysToSub) // binary subtraction
   {
      return Date(month, day - daysToSub, year);
   }

   void DisplayDate()
   {
      cout << month << " / " << day << " / " << year << endl;
   }
};

int main()
{
   Date Holiday (12, 25, 2016);
   cout << "Holiday on: ";
   Holiday.DisplayDate ();

   Date PreviousHoliday (Holiday - 19);
   cout << "Previous holiday on: ";
   PreviousHoliday.DisplayDate();

   Date NextHoliday(Holiday + 6);
   cout << "Next holiday on: ";
   NextHoliday.DisplayDate ();

   return 0;
}

字符串使用+拼接

优化MyString类: 定义运算符+

MyString operator+ (const MyString& addThis)
{
 MyString newString;
 if (addThis.buffer != NULL)
 {
 newString.buffer = new char[GetLength() + strlen(addThis.buffer) + 1];
 strcpy(newString.buffer, buffer);
 strcat(newString.buffer, addThis.buffer);
 }
 return newString;
}

重载==和!=运算符

定义这两个运算符之前,编译器会直接比较二进制数据,简单对象可以正常返回正确结果。但是如果有char* 等指针数据,我们需要比较的是其指向的数据,而不是指针成员的地址值。

#include <iostream>
using namespace std;

class Date
{
private:
    int day, month, year;

public:
    Date(int inMonth, int inDay, int inYear)
        : month(inMonth), day(inDay), year(inYear) {}

   bool operator== (const Date& compareTo)
   {
      return ((day == compareTo.day) 
            && (month == compareTo.month) 
           && (year == compareTo.year));
   }

   bool operator!= (const Date& compareTo)
   {
      return !(this->operator==(compareTo));
   }

   void DisplayDate()
   {
       cout << month << " / " << day << " / " << year << endl;
   }
};
   
int main()
{
   Date holiday1 (12, 25, 2016);
   Date holiday2 (12, 31, 2016);

   cout << "holiday 1 is: ";
   holiday1.DisplayDate();
   cout << "holiday 2 is: ";
   holiday2.DisplayDate();

   if (holiday1 == holiday2)
      cout << "Equality operator: The two are on the same day" << endl;
   else
      cout << "Equality operator: The two are on different days" << endl;

   if (holiday1 != holiday2)
      cout << "Inequality operator: The two are on different days" << endl;
   else
      cout << "Inequality operator: The two are on the same day" << endl;

   return 0;
}

/**
holiday 1 is: 12 / 25 / 2016
holiday 2 is: 12 / 31 / 2016
Equality operator: The two are on different days
Inequality operator: The two are on different days 
*/

小于(<)、大于(>)、小于等于(<=)和大于等于(>=)运算符大致同上 覆写,并指定自己的一套比较标准,返回结果即可。

复制赋值运算符=

复制构造函数是通过复制场景来创建一个类时调用,这个是将一个类通过复制运算符赋给另一个类时使用。

需要清空当前类里需要覆盖的部分,使用新值。

#include <iostream>
#include <string.h>
using namespace std;

class MyString
{
private:
   char* buffer;
   
public:
   MyString(const char* initialInput)
   {
      if(initialInput != NULL)
      {
         buffer = new char [strlen(initialInput) + 1];
         strcpy(buffer, initialInput);
     }
      else 
         buffer = NULL;
   }

   // Copy assignment operator
   MyString& operator= (const MyString& CopySource)
   {
      if ((this != &CopySource) && (CopySource.buffer != NULL))
      {
         if (buffer != NULL)
          delete[] buffer;

         // ensure deep copy by first allocating own buffer 
         buffer = new char [strlen(CopySource.buffer) + 1];

         // copy from the source into local buffer
         strcpy(buffer, CopySource.buffer);
      }

     return *this;
   }

   operator const char*()
   {
      return buffer;
   }

   ~MyString()
   {
       delete[] buffer;
   }

   MyString(const MyString& CopySource)
   {
       cout << "Copy constructor: copying from MyString" << endl;

       if (CopySource.buffer != NULL)
       {
           // ensure deep copy by first allocating own buffer 
           buffer = new char[strlen(CopySource.buffer) + 1];

           // copy from the source into local buffer
           strcpy(buffer, CopySource.buffer);
       }
       else
           buffer = NULL;
   }
};
   
int main()
{
   MyString string1("Hello ");
   MyString string2(" World");

   cout << "Before assignment: " << endl;
   cout << string1 << string2 << endl;
   string2 = string1;
   cout << "After assignment string2 = string1: " << endl;
   cout << string1 << string2 << endl;

   return 0;
}

/**
Before assignment:
Hello World
After assignment string2 = string1:
Hello Hello 
*/

如果您编写的类管理着动态分配的资源(如使用 new 分配的数组),除构造函数和析构函数外,请务必实现复制构造函数和复制赋值运算符。

如果没有解决对象被复制时出现的资源所有权问题,您的类就是不完整的,使用时甚至会影响应用程序的稳定性

下标运算符[]

编写封装了动态数组的类(如封装了 char* buffer 的 MyString)时,通过实现下标运算符,可轻松地随机访问缓冲区中的各个字符。

#include <iostream>
#include <string>
#include <string.h>
using namespace std;

class MyString
{
private:
   char* Buffer;
   
   // private default constructor
   MyString() {}

public:
   // constructor
   MyString(const char* InitialInput)
   {
      if(InitialInput != NULL)
      {
         Buffer = new char [strlen(InitialInput) + 1];
         strcpy(Buffer, InitialInput);
      }
      else 
         Buffer = NULL;
   }

   MyString operator + (const char* stringIn)
   {
      string strBuf(Buffer);
      strBuf += stringIn;
      MyString ret(strBuf.c_str());
      return ret;
   }

   // Copy constructor
   MyString(const MyString& CopySource)
   {
      if(CopySource.Buffer != NULL)
      {
         // ensure deep copy by first allocating own buffer 
         Buffer = new char [strlen(CopySource.Buffer) + 1];

         // copy from the source into local buffer
         strcpy(Buffer, CopySource.Buffer);
      }
      else 
         Buffer = NULL;
   }

   // Copy assignment operator
   MyString& operator= (const MyString& CopySource)
   {
      if ((this != &CopySource) && (CopySource.Buffer != NULL))
      {
         if (Buffer != NULL)
          delete[] Buffer;

         // ensure deep copy by first allocating own buffer 
         Buffer = new char [strlen(CopySource.Buffer) + 1];

         // copy from the source into local buffer
         strcpy(Buffer, CopySource.Buffer);
      }

     return *this;
   }

   const char& operator[] (int Index) const
   {
      if (Index < GetLength())
         return Buffer[Index];
   }
  
   // Destructor
   ~MyString()
   {
      if (Buffer != NULL)
         delete [] Buffer;
   }

   int GetLength() const
   {
      return strlen(Buffer);
   }

   operator const char*()
   {
      return Buffer;
   }
};

int main()
{
   cout << "Type a statement: ";
   string strInput;
   getline(cin, strInput);

   MyString youSaid(strInput.c_str());

   cout << "Using operator[] for displaying your input: " << endl;
   for (int index = 0; index < youSaid.GetLength(); ++index)
      cout << youSaid[index] << " ";
   cout << endl;

   cout << "Enter index 0 - " << youSaid.GetLength() - 1 << ": ";
   int index = 0;
   cin >> index;
   cout << "Input character at zero-based position: " << index;
   cout << " is: " << youSaid[index] << endl;

   return 0;
}

/**

Type a statement: Hey subscript operators[] are fabulous
Using operator[] for displaying your input:
H e y s u b s c r i p t o p e r a t o r s [ ] a r e f a b u l o u s
Enter index 0 - 37: 2
Input character at zero-based position: 2 is: y
*/

定义下标运算符时,如果不允许修改内部数组,则将返回的引用定义为const类型。将函数类型定义为const类型,禁止通过这个函数来修改其他的类成员属性。

如果需要通过这个函数去修改内部属性,则不定义成const。

函数运算符 operator()

operator() 让对象像函数,被称为函数运算符。函数运算符用于标准模板库(STL)中,通常是 STL算法中,其用途包括决策。根据使用的操作数数量,这样的函数对象通常称为单目谓词或双目谓词。

#include <iostream>
#include <string>
using namespace std;

class Display
{
public:
   void operator () (string input) const
   {
      cout << input << endl;
   }
};

int main ()
{
   Display displayFuncObj;

   // equivalent to displayFuncObj.operator () ("Display this string!");
   displayFuncObj ("Display this string!"); 

   return 0;
}

这个运算符也称为 operator()函数,而 Display 对象也称为函数对象或 functor。

移动构造函数

从上述代码可知,相比于常规赋值构造函数和复制赋值运算符的声明,移动构造函数和移动赋值运算符的不同之处在于,输入参数的类型为 Sample&&。另外,由于输入参数是要移动的源对象,因此不能使用 const 进行限定,因为它将被修改。返回类型没有变,因为它们分别是构造函数和赋值运算符的重载版本。 在需要创建临时右值时,遵循 C++的编译器将使用移动构造函数(而不是复制构造函数)和移动赋值运算符(而不是复制赋值运算符)。

移动构造函数和移动赋值运算符的实现中,只是将资源从源移到目的地,而没有进行复制。

在执行下面的拼接加赋值sayHelloAgain = Hello + World + CPP这个过程中,首先调用Hello的+运算符函数,将World的内容加进来,创造一个临时对象(Hello World),同理再把cpp加进来,创造另一个临时对象(Hello World of C++)。然后编译器会调用sayHelloAgain的移动构造函数,将这个临时对象的内容赋给sayHelloAgain,然后删除临时对象的buffer。这个过程只使用了一次移动构造函数。

完整示例:

#include <iostream>
#include <string.h>
using namespace std;

class MyString
{
private:
   char* buffer;

   MyString(): buffer(NULL) // private default constructor
   {
      cout << "Default constructor called" << endl;
   }

public:
   MyString(const char* initialInput) // constructor
   {
      cout << "Constructor called for: " << initialInput << endl;
      if(initialInput != NULL)
      {
         buffer = new char [strlen(initialInput) + 1];
         strcpy(buffer, initialInput);
      }
      else
         buffer = NULL;
   }

   MyString(MyString&& moveSrc) // move constructor
   {
      cout << "Move constructor moves: " << moveSrc.buffer << endl;
      if(moveSrc.buffer != NULL)
      {
         buffer = moveSrc.buffer; // take ownership i.e.  'move'
         moveSrc.buffer = NULL;   // free move source
      }
    }

   MyString& operator= (MyString&& moveSrc) // move assignment op.
   {
      cout << "Move assignment op. moves: " << moveSrc.buffer << endl;
      if((moveSrc.buffer != NULL) && (this != &moveSrc))
      {
         delete[] buffer; // release own buffer

         buffer = moveSrc.buffer; // take ownership i.e.  'move'
         moveSrc.buffer = NULL;   // free move source
      }

      return *this;
   }

   MyString(const MyString& copySrc) // copy constructor
   {
      cout << "Copy constructor copies: " << copySrc.buffer << endl;
      if (copySrc.buffer != NULL)
      {
         buffer = new char[strlen(copySrc.buffer) + 1];
         strcpy(buffer, copySrc.buffer);
      }
      else
         buffer = NULL;
   }

   MyString& operator= (const MyString& copySrc) // Copy assignment op.
   {
      cout << "Copy assignment op. copies: " << copySrc.buffer << endl;
      if ((this != &copySrc) && (copySrc.buffer != NULL))
      {
         if (buffer != NULL)
            delete[] buffer;

         buffer = new char[strlen(copySrc.buffer) + 1];
         strcpy(buffer, copySrc.buffer);
      }

      return *this;
   }

   ~MyString() // destructor
   {
      if (buffer != NULL)
         delete[] buffer;
   }

   int GetLength()
   {
      return strlen(buffer);
   }

   operator const char*()
   {
      return buffer;
   }
   
   MyString operator+ (const MyString& addThis)
   {
      cout << "operator+ called: " << endl;
      MyString newStr;

      if (addThis.buffer != NULL)
      {
         newStr.buffer = new char[GetLength()+strlen(addThis.buffer)+1];
         strcpy(newStr.buffer, buffer);
         strcat(newStr.buffer, addThis.buffer);
      }

      return newStr;
   }
};

int main()
{
   MyString Hello("Hello ");
   MyString World("World");
   MyString CPP(" of C++");

   MyString sayHelloAgain ("overwrite this");
   sayHelloAgain = Hello + World + CPP;

   return 0;
}

/*
Without move constructor and move assignment operator:
Constructor called for: Hello
Constructor called for: World
Constructor called for:  of C++
Constructor called for: overwrite this
operator+ called:
Default constructor called
Copy constructor to copy from: Hello World
operator+ called:
Default constructor called
Copy constructor to copy from: Hello World of C++
Copy assignment operator to copy from: Hello World of C++

With move constructor and move assignment operators:
Constructor called for: Hello
Constructor called for: World
Constructor called for:  of C++
Constructor called for: overwrite this
operator+ called:
Default constructor called
Move constructor to move from: Hello World
operator+ called:
Default constructor called
Move constructor to move from: Hello World of C++
Move assignment operator to move from: Hello World of C++
*/

学习过程中复制构造函数的一个问题

当使用临时对象当作函数参数进行值传递时,将不会走复制构造函数,而是直接使用这个对象。

自定义字面量

涉及热力学的温度声明,采用如下方式:

Temperature k1 = 32.15_F;
Temperature k2 = 0.0_C; ReturnType operator "" YourLiteral(ValueType value)
{
 // conversion code here
} 

实例:

#include <iostream>
using namespace std;

struct Temperature
{
   double Kelvin;
   Temperature(long double kelvin) : Kelvin(kelvin) {}
};

Temperature operator"" _C(long double celcius)
{
   return Temperature(celcius + 273);
}

Temperature operator "" _F(long double fahrenheit)
{
   return Temperature((fahrenheit + 459.67) * 5 / 9);
}

int main()
{
   Temperature k1 = 31.73_F;
   Temperature k2 = 0.0_C;

   cout << "k1 is " << k1.Kelvin << " Kelvin" << endl;
   cout << "k2 is " << k2.Kelvin << " Kelvin" << endl;

   return 0;
}

/**
k1 is 273 Kelvin
k2 is 273 Kelvin 
*/

static_cast类型转换

static_cast 用于在相关类型的指针之间进行转换,还可显式地执行标准数据类型的类型转换—这种转换原本将自动或隐式地进行。

将 Derived转换为 Base被称为向上转换,无需使用任何显式类型转换运算符就能进行这种转换:

Derived objDerived;
Base* objBase = &objDerived; // ok!

将 Base转换为 Derived被称为向下转换,如果不使用显式类型转换运算符,就无法进行这种转换:

Derived objDerived;
Base* objBase = &objDerived; // Upcast -> ok!
Derived* objDer = objBase; // Error: Downcast needs explicit cast

可以利用static_cast进行向下转换,而不会报错。

Base* objBase = new Base();
Derived* objDer = static_cast<Derived*>(objBase); // Still no errors!

然而,static_cast 只验证指针类型是否相关,而不会执行任何运行阶段检查。

因此 objDer ->DerivedFunction() 能够通过编译,但在运行阶段可能导致意外结果。

dynamic_cast隐式转换

给定一个指向基类对象的指针,程序员可使用 dynamic_cast 进行类型转换,并在使用指针前检查指针指向的目标对象的类型。

Base* objBase = new Derived();
// Perform a downcast
Derived* objDer = dynamic_cast<Derived*>(objBase);
if(objDer) // Check for success of the cast
objDer->CallDerivedFunction (); 

这种在运行阶段识别对象类型的机制称为

运行阶段类型识别(runtime type identification,RTTI)。

#include <iostream>
using namespace std; 
  
class Fish
{
public:
   virtual void Swim()
   {
      cout << "Fish swims in water" << endl;
   }

   // base class should always have virtual destructor
   virtual ~Fish() {}   
};

class Tuna: public Fish
{
public:
   void Swim()
   {
      cout << "Tuna swims real fast in the sea" << endl;
   }

   void BecomeDinner()
   {
      cout << "Tuna became dinner in Sushi" << endl;
   }
};

class Carp: public Fish
{
public:
   void Swim()
   {
      cout << "Carp swims real slow in the lake" << endl;
   }

   void Talk()
   {
      cout << "Carp talked carp!" << endl;
   }
};

void DetectFishType(Fish* objFish)
{
   Tuna* objTuna = dynamic_cast <Tuna*>(objFish);
   if (objTuna)
   {
      cout << "Detected Tuna. Making Tuna dinner: " << endl;
      objTuna->BecomeDinner();   // calling Tuna::BecomeDinner
   }

   Carp* objCarp = dynamic_cast <Carp*>(objFish);
   if(objCarp)
   {
      cout << "Detected Carp. Making carp talk: " << endl;
      objCarp->Talk();  // calling Carp::Talk
   }

   cout << "Verifying type using virtual Fish::Swim: " << endl;
   objFish->Swim(); // calling virtual function Swim
}

int main()
{
   Carp myLunch;
   Tuna myDinner;

   DetectFishType(&myDinner);
   DetectFishType(&myLunch);

   return 0;
}

务必检查 dynamic_cast 的返回值,看它是否有效。如果返回值为 NULL,说明转换失败。

reinterpret_cast强制转换

可以做任何类型转换。

这种类型转换实际上是强制编译器接受 static_cast 通常不允许的类型转换,通常用于低级程序(如驱动程序),在这种程序中,需要将数据转换为 API(应用程序编程接口)能够接受的简单类型(例如,有些 OS 级 API 要求提供的数据为 BYTE 数组,即 unsigned char*)。

由于其他 C++类型转换运算符都不允许执行这种有悖类型安全的转换,因此除非万不得已,否则不要使用 reinterpret_cast 来执行不安全(不可移植)的转换。

const_cast将const类型转换成非const

某些情况,使用的类我们无法修改,其内部如果使用了不合理的非const函数,外部的const对象指针无法使用非const函数,就可以将外部的const对象转换成非const,实现调用。

void DisplayAllData (const SomeClass* data)
{
 // data->DisplayMembers(); Error: attempt to invoke a non-const function!
 SomeClass* pCastedData = const_cast<SomeClass*>(data);
 pCastedData->DisplayMembers(); // Allowed!
}

使用类型转换需要注意

在现代 C++中,除 dynamic_cast 外的类型转换都是可以避免的。仅当需要满足遗留应用程序的需求时,才需要使用其他类型转换运算符。在这种情况下,程序员通常倾向于使用 C 风格类型转换而不是C++类型转换运算符。重要的是,应尽量避免使用类型转换;而一旦使用类型转换,务必要知道幕后发生的情况。

预处理

顾名思义,预处理器在编译器之前运行,换句话说,预处理器根据程序员的指示,决定实际要编译的内容。预处理器编译指令都以#打头。

宏定义常量

#include <iostream>
#include<string>
using namespace std;

#define ARRAY_LENGTH 25
#define PI 3.1416
#define MY_DOUBLE double
#define FAV_WHISKY "Jack Daniels"

/*
// Superior alternatives (comment those above when you uncomment these)
const int ARRAY_LENGTH = 25;
const double PI = 3.1416;
typedef double MY_DOUBLE;
const char* FAV_WHISKY = "Jack Daniels";
*/

int main()
{
   int MyNumbers [ARRAY_LENGTH] = {0};
   cout << "Array's length: " << sizeof(MyNumbers) / sizeof(int) << endl;

   cout << "Enter a radius: ";
   MY_DOUBLE Radius = 0;
   cin >> Radius;
   cout << "Area is: " << PI * Radius * Radius << endl;

   string FavoriteWhisky (FAV_WHISKY);
   cout << "My favorite drink is: " << FAV_WHISKY << endl;

   return 0;
}

定义常量时,更好的选择是使用关键字 const 和数据类型,因此下面的定义好得多:

const int ARRAY_LENGTH = 25;
const double PI = 3.1416;
const char* FAV_WHISKY = "Jack Daniels";
typedef double MY_DOUBLE; // typedef aliases a type

最常用的宏功能

如果在头文件 class1.h 中声明了一个类,而这个类将 class2.h 中声明的类作为其成员,则需要在 class1.h 中包含 class2.h。如果设计非常复杂,即第二个类需要第一个类,则在 class2.h 中也需要包含 class1.h!然而,在预处理器看来,两个头文件彼此包含对方会导致递归问题。

为了防止循环引用,可以使用#ifndef 强制这种引用只执行一次:

#ifndef HEADER1_H _//multiple inclusion guard:
#define HEADER1_H_ // preprocessor will read this and following lines once
#include <header2.h>
class Class1{ 
// class members
};
#endif // end of header1.h

header2.h 与此类似,但宏定义不同,且包含的是<header1.h>:

#ifndef HEADER2_H_  //multiple inclusion guard
#define HEADER2_H_
#include<header1.h>
class Class2{ 
// class members
};
#endif // end of header2.h 

#ifndef 可读作 if-not-defined。这是一个条件处理命令,让预处理器仅在标识符未定义时才继续。#endif 告诉预处理器,条件处理指令到此结束。

因此,预处理器首次处理 header1.h 并遇到#ifndef 后,发现宏 HEADER1H_还未定义,因此继续处理。#ifndef 后面的第一行定义了宏 HEADER1_H,确保预处理器再次处理该文件时,将在遇到包含#ifndef 的第一行时结束,因为其中的条件为 false.

#define定义宏函数

例如平方计算,#define SQUARE(x) ((x) * (x)) 相比于常规函数调用,宏函数的优点在于,它们将在编译前就地展开,因此在有些情况下有助于改善代码的性能。而且一个宏可使用另一个宏。比如计算面积的宏函数可以使用定义的宏变量PI。 有个缺点是宏函数不考虑数据类型,返回值精度依赖输入的精度。

为什么要加这么多括号?

因为宏是最简单的替换,不会提前计算。如果去掉括号: #define AREA_CIRCLE(r) (PIrr) 如果使用类似于下面的语句调用这个宏,结果将如何呢? cout « AREA_CIRCLE (4+6); 展开后,编译器看到的语句如下: cout « (PI4+64+6); // not the same as PI1010 根据运算符优先级,将先执行乘法运算,再执行加法运算,因此编译器将这样计算面积: cout « (PI*4+24+6); // 42.5664 (which is incorrect) 在省略了括号的情况下,简单的文本替换破坏了编程逻辑!

assert宏

可以插入到某些地方来验证执行结果。需要提前包含<assert.h>

assert (expression that evaluates to true or false); 

如果条件不满足,它将抛出一个错误信息。

由于断言在发布模式下不可用,对于对应用程序正确运行至关重要的检查(如检查dynamic_cast 的返回值),为了确保它们在发布模式下也会执行,应使用 if 语句,这很重要。断言可帮助您找出问题,但不能因此不在代码中对指针做必要的检查。

使用宏的应该和不应该事项

  1. 尽可能不要自己编写宏函数。
  2. 尽可能使用 const 变量,而不是宏常量。
  3. 请牢记,宏并非类型安全的,预处理器不执行类型检查。
  4. 在宏函数的定义中,别忘了使用括号将每个变量括起。
  5. 为了在头文件中避免多次包含,别忘了使用#ifndef、#define 和#endif。
  6. 别忘了在代码中大量使用 assert( ),它们在发行版本中将被禁用,但对提高代码的质量很有帮助。

模板

模板让程序员能够定义一种适用于不同类型对象的行为。这听起来有点像宏(参见前面用于判断两个数中哪个更大的简单宏 MAX),但宏不是类型安全的,而模板是类型安全的。

编写一个比较大小的模板函数,它的接收的类型可以根据传参的类型自动生成多个重载函数。

#include<iostream>
#include<string>
using namespace std;

template <typename Type>
const Type& GetMax (const Type& value1, const Type& value2)
{
    if (value1 > value2)
        return value1;
    else
        return value2;
}

template <typename Type>
void DisplayComparison(const Type& value1, const Type& value2)
{
   cout << "GetMax(" << value1 << ", " << value2 << ") = ";
   cout << GetMax(value1, value2) << endl;
}

int main()
{
   int num1 = -101, num2 = 2011;
   DisplayComparison<int>(num1, num2);

   double d1 = 3.14, d2 = 3.1416;
   DisplayComparison(d1, d2);

   string name1("Jack"), name2("John");
   DisplayComparison(name1, name2);

   return 0;
}

上述代码将导致编译器生成模板函数 GetMax 的两个版本。 如果进行调用的参数不一致,比如传一个int和srting类型一起比较大小,将导致编译错误。

模板类

类是设计对象的蓝图,而模板类就是蓝图的蓝图。

可以在类里面设置模板参数,让同一个类的同一个字段使用不同的类型来表示。

template <typename T1, typename T2>
class HoldsPair
{
private:
 T1 value1;
 T2 value2;
public:
 // Constructor that initializes member variables
 HoldsPair (const T1& val1, const T2& val2)
 {
 value1 = val1;
 value2 = val2;
 };
 // ... Other member functions
};

// 在这里,类 HoldsPair 接受两个模板参数,参数名分别为 T1 和 T2。可使用这个类来存储两个类型
// 相同或不同的对象,如下所示:
// A template instantiation that pairs an int with a double
HoldsPair <int, double> pairIntDouble (6, 1.99);
// A template instantiation that pairs an int with an int
HoldsPair <int, int> pairIntDouble (6, 500); 

还可以设置默认类型简化使用,如果实例使用的和默认类型相同,则可以简化对象的声明方式。

// template with default params: int & double
template <typename T1=int, typename T2=double>
class HoldsPair
{
private:
   T1 value1;
   T2 value2;
public:
   HoldsPair(const T1& val1, const T2& val2) // constructor
      : value1(val1), value2(val2) {}
 
   // Accessor functions
   const T1 & GetFirstValue () const 
   {
      return value1;
   }

   const T2& GetSecondValue () const
   {
      return value2;
   }
};
   
#include <iostream>
using namespace std;

int main ()
{
   HoldsPair <> pairIntDbl (300, 10.09);
   HoldsPair <short, const char*> pairShortStr(25, "Learn templates, love C++");

   cout << "The first object contains -" << endl;
   cout << "Value 1: " << pairIntDbl.GetFirstValue () <<  endl;
   cout << "Value 2: " << pairIntDbl.GetSecondValue () << endl;

   cout << "The second object contains -" << endl; 
   cout << "Value 1: " << pairShortStr.GetFirstValue () <<  endl;
   cout << "Value 2: " << pairShortStr.GetSecondValue () << endl;

   return 0;
}

模板实例化和具体化

定义但不使用的模板,编译器将忽略。因此,对模板来说,实例化指的是使用一个或多个模板参数来创建特定的类型。

HoldsPair<int, double> pairIntDbl;

相当于编译器使用这个模板创建了一个类。

另一方面,在有些情况下,使用特定的类型实例化模板时,需要显式地指定不同的行为。这就是具体化模板,即为特定的类型指定行为。

#include <iostream>
using namespace std;

template <typename T1 = int, typename T2 = double>
class HoldsPair
{
private:
   T1 value1;
   T2 value2;
public:
   HoldsPair(const T1& val1, const T2& val2) // constructor
      : value1(val1), value2(val2) {}

   // Accessor functions
   const T1 & GetFirstValue() const;
   const T2& GetSecondValue() const;
};

// specialization of HoldsPair for types int & int here
template<> class HoldsPair<int, int>
{
private:
   int value1;
   int value2;
   string strFun;
public:
   HoldsPair(const int& val1, const int& val2) // constructor
      : value1(val1), value2(val2) {}

   const int & GetFirstValue() const
   {
      cout << "Returning integer " << value1 << endl;
      return value1;
   }
};
   
int main()
{
   HoldsPair<int, int> pairIntInt(222, 333);
   pairIntInt.GetFirstValue();

   return 0;
}

带静态变量的模板类

#include <iostream>
using namespace std;

template <typename T>
class TestStatic
{
public:
   static int staticVal;
};
   
// static member initialization
template<typename T> int TestStatic<T>::staticVal;

int main()
{
   TestStatic<int> intInstance;
   cout << "Setting staticVal for intInstance to 2011" << endl;
   intInstance.staticVal = 2011;

   TestStatic<double> dblnstance;
   cout << "Setting staticVal for Double_2 to 1011" << endl;
   dblnstance.staticVal = 1011;

   cout << "intInstance.staticVal = " << intInstance.staticVal << endl;
   cout << "dblnstance.staticVal = " << dblnstance.staticVal << endl;

   return 0;
}

/**
Setting staticVal for intInstance to 2011
Setting staticVal for Double_2 to 1011
intInstance.staticVal = 2011
dblnstance.staticVal = 1011
*/

也就是说,如果模板类包含静态成员,该成员将在针对 int 具体化的所有实例之间共享;同样,它还将在针对 double 具体化的所有实例之间共享,且与针对 int 具体化的实例无关。换句话说,可以认为编译器创建了两个版本的 x:x_int 用于针对 int 具体化的实例,而 x_double 针对 double 具体化的实例。

参数数量可变的模板函数

参数数量可变的模板是 2014 年发布的 C++14 新增的。

#include <iostream>
using namespace std;

template <typename Res, typename ValType>
void Sum(Res& result, ValType& val)
{
   result = result + val;
}

template <typename Res, typename First, typename... Rest> 
void Sum(Res& result, First val1, Rest... numN)
{
   result = result + val1;
   return Sum(result, numN ...);
}

int main()
{
   double dResult = 0;
   Sum (dResult, 3.14, 4.56, 1.1111);
   cout << "dResult = " << dResult << endl;

   string strResult;
   Sum (strResult, "Hello ", "World");
   cout << "strResult = " << strResult.c_str() << endl;

   return 0;
}

您可能注意到了,在前面的代码示例中,使用了省略号…。在 C++中,模板中的省略号告诉编译器,默认类或模板函数可接受任意数量的模板参数,且这些参数可为任何类型。

元组

通过索引访问的一种数据结构,内部可以存储多种不同的数据类型。

#include <iostream>
#include <tuple>
#include <string>
using namespace std;

template <typename tupleType>
void DisplayTupleInfo(tupleType& tup)
{
   const int numMembers = tuple_size<tupleType>::value;
   cout << "Num elements in tuple: " << numMembers << endl;
   cout << "Last element value: " << get<numMembers - 1>(tup) << endl;
}

int main()
{
   tuple<int, char, string> tup1(make_tuple(101, 's', "Hello Tuple!"));
   DisplayTupleInfo(tup1);

   auto tup2(make_tuple(3.14, false));
   DisplayTupleInfo(tup2);

   auto concatTup(tuple_cat(tup2, tup1)); // contains tup2, tup1 members
   DisplayTupleInfo(concatTup);

   double pi;
   string sentence;
   tie(pi, ignore, ignore, ignore, sentence) = concatTup;
   cout << "Unpacked! Pi: " << pi << " and \"" << sentence << "\"" << endl;

    return 0;
}

static_assert不满足条件直接禁止编译

static_assert 是 C++11 新增的一项功能,让您能够在不满足指定条件时禁止编译。这好像不可思议,但对模板类来说很有用。例如,您可能想禁止针对 int 实例化模板类,为此可使用 static_assert,它是一种编译阶段断言,可用于在开发环境(或控制台中)显示一条自定义消息。

template <typename T>
class EverythingButInt
{
public:
   EverythingButInt()
   {
      static_assert(sizeof(T) != sizeof(int), "No int please!");
   }
};

int main()
{
   EverythingButInt<int> test;

   return 0;
}

待补充STL标准库