介绍

C++ 是一种中级语言,它是由 Bjarne Stroustrup 于 1979 年在贝尔实验室开始设计开发的。C++ 进一步扩充和完善了 C 语言,是一种面向对象的程序设计语言。C++ 可运行于多种平台上,如 Windows、MAC 操作系统以及 UNIX 的各种版本。

面向对象程序设计

C++ 完全支持面向对象的程序设计,包括面向对象开发的四大特性:

  • 封装
  • 抽象
  • 继承
  • 多态

    入门

    程序结构

    ```cpp // C++ 语言定义了一些头文件,这些头文件包含了程序中必需的或有用的信息。

    include

    // 告诉编译器使用 std 命名空间。命名空间是 C++ 中一个相对新的概念。 using namespace std;

// main() 是程序开始执行的地方 int main() { // 输出 Hello World cout << “Hello World”; // 终止 main( )函数,并向调用进程返回值 0。 return 0; }

  1. C++ 中,分号是语句结束符。也就是说,每个语句必须以分号结束。它表明一个逻辑实体的结束。
  2. <a name="7huJs"></a>
  3. ### 数据类型
  4. C++ 为程序员提供了种类丰富的内置数据类型和用户自定义的数据类型。下表列出了七种基本的 C++ 数据类型:<br />不同系统会有所差异,一字节为 8 位。
  5. | 类型 | 关键字 | 范围 |
  6. | :--- | :--- | :--- |
  7. | 布尔型 | bool | |
  8. | 字符型 | char | 1 个字节 -128 127 或者 0 255 |
  9. | 整型 | int | 4 个字节 -2147483648 2147483647 |
  10. | 浮点型 | float | 4 个字节 精度型占4个字节(32位)内存空间,+/- 3.4e +/- 38 (~7 个数字) |
  11. | 双浮点型 | double | 8 个字节 双精度型占8 个字节(64位)内存空间,+/- 1.7e +/- 308 (~15 个数字) |
  12. | 无类型 | void | |
  13. | 宽字符型 | wchar_t | 2 4 个字节 1 个宽字符 |
  14. <a name="SvHK2"></a>
  15. ### 变量类型
  16. - bool 存储值 true false
  17. - char 通常是一个字符(八位)。这是一个整数类型。
  18. - float 单精度浮点值。单精度是这样的格式,1位符号,8位指数,23位小数。
  19. - double 双精度浮点值。双精度是1位符号,11位指数,52位小数。
  20. - void 表示类型的缺失。
  21. <a name="XZfXg"></a>
  22. ### 变量声明
  23. 变量声明向编译器保证变量以给定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。<br />当您使用多个文件且只在其中一个文件中定义变量时(定义变量的文件在程序连接时是可用的),变量声明就显得非常有用。您可以使用 **extern** 关键字在任何地方声明一个变量。
  24. ```cpp
  25. #include <iostream>
  26. using namespace std;
  27. // 变量声明
  28. extern int a, b;
  29. extern float f;
  30. int main ()
  31. {
  32. // 变量定义
  33. int a, b;
  34. float f;
  35. // 实际初始化
  36. a = 10;
  37. c = a + b;
  38. return 0;
  39. }

变量作用域

  • 在函数或一个代码块内部声明的变量,称为局部变量。
  • 在函数参数的定义中声明的变量,称为形式参数。
  • 在所有函数外部声明的变量,称为全局变量。

    常量

  • 使用 #define 预处理器。

  • 使用 const 关键字。 ```cpp

    define LENGTH 10

    define NEWLINE ‘\n’

const int LENGTH = 10; const char NEWLINE = ‘\n’;

请注意,把常量定义为大写字母形式,是一个很好的编程实践。
<a name="RDFUn"></a>
### 修饰符

- 修饰符 signed、unsigned、long 和 short 可应用于整型
- signed 和 unsigned 可应用于字符型
- long 可应用于双精度型。

C++ 允许使用速记符号来声明无符号短整数或无符号长整数。您可以不写 int,只写单词 unsigned、short 或 unsigned、long,int 是隐含的。
<a name="NNOQv"></a>
### [运算符](https://www.runoob.com/cplusplus/cpp-operators.html)
<a name="6V9vT"></a>
#### 杂项运算符
| 运算符 | 描述 |
| :--- | :--- |
| sizeof | [sizeof 运算符](https://www.runoob.com/cplusplus/cpp-sizeof-operator.html)返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。 |
| Condition ? X : Y | [条件运算符](https://www.runoob.com/cplusplus/cpp-conditional-operator.html)。如果 Condition 为真 ? 则值为 X : 否则值为 Y。 |
| , | [逗号运算符](https://www.runoob.com/cplusplus/cpp-comma-operator.html)会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。 |
| .(点)和 ->(箭头) | [成员运算符](https://www.runoob.com/cplusplus/cpp-member-operators.html)用于引用类、结构和共用体的成员。 |
| Cast | [强制转换运算符](https://www.runoob.com/cplusplus/cpp-casting-operators.html)把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。 |
| & | [指针运算符 &](https://www.runoob.com/cplusplus/cpp-pointer-operators.html) 返回变量的地址。例如 &a; 将给出变量的实际地址。 |
| * | [指针运算符 *](https://www.runoob.com/cplusplus/cpp-pointer-operators.html) 指向一个变量。例如,*var; 将指向变量 var。 |

<a name="eC7D6"></a>
### 存储类
<a name="3LXrp"></a>
#### **1. static** 存储类
**static** 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。<br />static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
<a name="Bbj5c"></a>
#### 2. extern 存储类
**extern** 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 'extern' 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
```cpp
#include <iostream>

// 函数声明 
void func(void);
// 全局函数
extern void write_extern();
// 全局变量
extern int count;
 // 全局变量
static int count = 10;

int main()
{
    while(count--)
    {
       func();
    }
    return 0;
}
// 函数定义
void func( void )
{
    static int i = 5; // 局部静态变量
    i++;
    std::cout << "变量 i 为 " << i ;
    std::cout << " , 变量 count 为 " << count << std::endl;
}

循环

#include <iostream>
using namespace std;

int main ()
{
   // for 循环执行
   for( int a = 10; a < 20; a = a + 1 )
   {
       cout << "a 的值:" << a << endl;
   }

    // for 数组循环
    int my_array[5] = {1, 2, 3, 4, 5};
    // 每个数组元素乘于 2
    for (int &x : my_array)
    {
        x *= 2;
        cout << x << endl;  
    }    

   return 0;
}

判断

#include <iostream>
using namespace std;

int main ()
{
   // 局部变量声明
   char grade = 'D';

   switch(grade)
   {
   case 'A' :
      cout << "很棒!" << endl; 
      break;
   case 'B' :
   case 'C' :
      cout << "做得好" << endl;
      break;
   case 'D' :
      cout << "您通过了" << endl;
      break;
   default :
      cout << "无效的成绩" << endl;
   }
   cout << "您的成绩是 " << grade << endl;

   return 0;
}

函数

函数默认值

当您定义一个函数,您可以为参数列表中后边的每一个参数指定默认值。当调用函数时,如果实际参数的值留空,则使用这个默认值。

#include <iostream>
using namespace std;

int sum(int a, int b=20)
{
  int result;

  result = a + b;

  return (result);
}

int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;
   int result;

   // 调用函数来添加值
   result = sum(a, b);
   cout << "Total value is :" << result << endl;

   // 再次调用函数
   result = sum(a);
   cout << "Total value is :" << result << endl;

   return 0;
}

指针调用

向函数传递参数的指针调用方法,把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。

#include <iostream>
using namespace std;

// 函数定义
void swap(int *x, int *y)
{
   int temp;
   temp = *x;    /* 保存地址 x 的值 */
   *x = *y;        /* 把 y 赋值给 x */
   *y = temp;    /* 把 x 赋值给 y */

   return;
}

int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;

   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;

   /* 调用函数来交换值
    * &a 表示指向 a 的指针,即变量 a 的地址 
    * &b 表示指向 b 的指针,即变量 b 的地址 
    */
   swap(&a, &b);

   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;

   return 0;
}

// 输出
/*
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100
*/

引用调用

向函数传递参数的引用调用方法,把引用的地址复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。

#include <iostream>
using namespace std;

// 函数定义
void swap(int &x, int &y)
{
   int temp;
   temp = x; /* 保存地址 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 x 赋值给 y  */

   return;
}

int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;

   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;

   /* 调用函数来交换值 */
   swap(a, b);

   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;

   return 0;
}

数组

声明数组

在 C++ 中要声明一个数组,需要指定元素的类型和元素的数量,如下所示:

type arrayName [ arraySize ];

// 声明一个类型为double的包含10个元素的数组balance
double balance[10];

初始化数组

// 使用一个初始化语句,如下所示:
// 大括号 { } 之间的值的数目不能大于我们在数组声明时在方括号 [ ] 中指定的元素数目。
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

// 如果您省略掉了数组的大小,数组的大小则为初始化时元素的个数。因此,如果:
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};

// 下面是一个为数组中某个元素赋值的实例:
balance[4] = 50.0;

访问数组元素

double salary = balance[9];

字符串

#include <iostream>
#include <cstring>

using namespace std;

int main ()
{
   char str1[11] = "Hello";
   char str2[11] = "World";
   char str3[11];
   int  len ;

   // 复制 str1 到 str3
   strcpy( str3, str1);
   cout << "strcpy( str3, str1) : " << str3 << endl;

   // 连接 str1 和 str2
   strcat( str1, str2);
   cout << "strcat( str1, str2): " << str1 << endl;

   // 连接后,str1 的总长度
   len = strlen(str1);
   cout << "strlen(str1) : " << len << endl;

   return 0;
}

执行结构:

strcpy( str3, str1) : Hello
strcat( str1, str2): HelloWorld
strlen(str1) : 10

String 类

C++ 标准库提供了 string 类类型,支持上述所有的操作,另外还增加了其他更多的功能。

#include <iostream>
#include <string>

using namespace std;

int main ()
{
   string str1 = "Hello";
   string str2 = "World";
   string str3;
   int  len ;

   // 复制 str1 到 str3
   str3 = str1;
   cout << "str3 : " << str3 << endl;

   // 连接 str1 和 str2
   str3 = str1 + str2;
   cout << "str1 + str2 : " << str3 << endl;

   // 连接后,str3 的总长度
   len = str3.size();
   cout << "str3.size() :  " << len << endl;

   return 0;
}

指针

学习 C++ 的指针既简单又有趣。通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。
每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。请看下面的实例,它将输出定义的变量地址:

#include <iostream>

using namespace std;

int main ()
{
   int  var1;
   char var2[10];

   cout << "var1 变量的地址: ";
   cout << &var1 << endl;

   cout << "var2 变量的地址: ";
   cout << &var2 << endl;

   return 0;
}

// 输出
// var1 变量的地址: 0xbfebd5c0
// var2 变量的地址: 0xbfebd5b6

什么是指针?

指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。
星号是用来指定一个变量是指针。以下是有效的指针声明:

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */

使用指针

#include <iostream>

using namespace std;

int main ()
{
   int  var = 20;   // 实际变量的声明
   int  *ip;        // 指针变量的声明

   ip = &var;       // 在指针变量中存储 var 的地址

   cout << "Value of var variable: ";
   cout << var << endl;

   // 输出在指针变量中存储的地址
   cout << "Address stored in ip variable: ";
   cout << ip << endl;

   // 访问指针中地址的值
   cout << "Value of *ip variable: ";
   cout << *ip << endl;

   return 0;
}

执行结果

Value of var variable: 20
Address stored in ip variable: 0xbfc601ac
Value of *ip variable: 20

指针详解

概念 描述
C++ Null 指针 C++ 支持空指针。NULL 指针是一个定义在标准库中的值为零的常量。
C++ 指针的算术运算 可以对指针进行四种算术运算:++、—、+、-
C++ 指针 vs 数组 指针和数组之间有着密切的关系。
C++ 指针数组 可以定义用来存储指针的数组。
C++ 指向指针的指针 C++ 允许指向指针的指针。
C++ 传递指针给函数 通过引用或地址传递参数,使传递的参数在调用函数中被改变。
C++ 从函数返回指针 C++ 允许函数返回指针到局部变量、静态变量和动态内存分配。

引用

引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

引用 vs 指针

引用很容易与指针混淆,它们之间有三个主要的不同:

  • 不存在空引用。引用必须连接到一块合法的内存。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。

    创建引用

    int i = 17;
    
    我们可以为 i 声明引用变量,如下所示: ```cpp // 在这些声明中,& 读作引用。

// 读作 “r 是一个初始化为 i 的整型引用” int& r = i; // 读作 “s 是一个初始化为 d 的 double 型引用” double& s = d;

<a name="XFKjf"></a>
#### 把引用作为参数
已经讨论了如何使用指针来实现引用调用函数。下面的实例使用了引用来实现引用调用函数。
```cpp
#include <iostream>
using namespace std;

// 函数声明
void swap(int& x, int& y);

int main ()
{
   // 局部变量声明
   int a = 100;
   int b = 200;

   cout << "交换前,a 的值:" << a << endl;
   cout << "交换前,b 的值:" << b << endl;

   /* 调用函数来交换值 */
   swap(a, b);

   cout << "交换后,a 的值:" << a << endl;
   cout << "交换后,b 的值:" << b << endl;

   return 0;
}

// 函数定义
void swap(int& x, int& y)
{
   int temp;
   temp = x; /* 保存地址 x 的值 */
   x = y;    /* 把 y 赋值给 x */
   y = temp; /* 把 x 赋值给 y  */

   return;
}

执行输出:

交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100

把引用作为返回值

当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。例如,请看下面这个简单的程序:

#include <iostream>

using namespace std;

double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0};

double& setValues( int i )
{
  return vals[i];   // 返回第 i 个元素的引用
}

// 要调用上面定义函数的主函数
int main ()
{

   cout << "改变前的值" << endl;
   for ( int i = 0; i < 5; i++ )
   {
       cout << "vals[" << i << "] = ";
       cout << vals[i] << endl;
   }

   setValues(1) = 20.23; // 改变第 2 个元素
   setValues(3) = 70.8;  // 改变第 4 个元素

   cout << "改变后的值" << endl;
   for ( int i = 0; i < 5; i++ )
   {
       cout << "vals[" << i << "] = ";
       cout << vals[i] << endl;
   }
   return 0;
}

编译输出:

改变前的值
vals[0] = 10.1
vals[1] = 12.6
vals[2] = 33.1
vals[3] = 24.1
vals[4] = 50
改变后的值
vals[0] = 10.1
vals[1] = 20.23
vals[2] = 33.1
vals[3] = 70.8
vals[4] = 50

当返回一个引用时,要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。

int& func() {
   int q;
   //! return q; // 在编译时发生错误
   static int x;
   return x;     // 安全,x 在函数作用域外依然是有效的
}

输入输出

C++ 的 I/O 发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做输入操作
如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做输出操作

I/O 库头文件

头文件 函数和描述
该文件定义了 cin、cout、cerrclog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。
该文件通过所谓的参数化的流操纵器(比如 setwsetprecision),来声明对执行标准化 I/O 有用的服务。
该文件为用户控制的文件处理声明服务。我们将在文件和流的相关章节讨论它的细节。

标准输出流(cout)

预定义的对象 cout 是 iostream 类的一个实例。cout 对象”连接”到标准输出设备,通常是显示屏。cout 是与流插入运算符 << 结合使用的,如下所示:

#include <iostream>

using namespace std;

int main( )
{
   char str[] = "Hello C++";

   cout << "Value of str is : " << str << endl;
}

标准输入流(cin)

预定义的对象 cin 是 iostream 类的一个实例。cin 对象附属到标准输入设备,通常是键盘。cin 是与流提取运算符 >> 结合使用的,如下所示:

#include <iostream>

using namespace std;

int main( )
{
   char name[50];

   cout << "请输入您的名称: ";
   cin >> name;
   cout << "您的名称是: " << name << endl;

}

流提取运算符 >> 在一个语句中可以多次使用,如果要求输入多个数据,可以使用如下语句:

cin >> name >> age;

这相当于下面两个语句:

cin >> name;
cin >> age;

标准错误流(cerr)

预定义的对象 cerr 是 iostream 类的一个实例。cerr 对象附属到标准错误设备,通常也是显示屏,但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。
cerr 也是与流插入运算符 << 结合使用的,如下所示:

#include <iostream>

using namespace std;

int main( )
{
   char str[] = "Unable to read....";

   cerr << "Error message : " << str << endl;
}

// output
// Error message : Unable to read....

标准日志流(clog)

预定义的对象 clog 是 iostream 类的一个实例。clog 对象附属到标准错误设备,通常也是显示屏,但是 clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出。

#include <iostream>

using namespace std;

int main( )
{
   char str[] = "Unable to read....";

   clog << "Error message : " << str << endl;
}

// output
// Error message : Unable to read....

数据结构

结构是 C++ 中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。

定义结构

为了定义结构,您必须使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型,struct 语句的格
式如下:

struct type_name {
    member_type1 member_name1;
    member_type2 member_name2;
    member_type3 member_name3;
    ...
} object_names;

下面是声明一个结构体类型 Books,变量为 book

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

访问结构成员

为了访问结构的成员,我们使用成员 访问运算符(.)

#include <iostream>
#include <cstring>

using namespace std;

// 声明一个结构体类型 Books 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

int main( )
{
   Books Book1;        // 定义结构体类型 Books 的变量 Book1
   Books Book2;        // 定义结构体类型 Books 的变量 Book2

   // Book1 详述
   strcpy( Book1.title, "C++ 教程");
   strcpy( Book1.author, "Runoob"); 
   strcpy( Book1.subject, "编程语言");
   Book1.book_id = 12345;

   // Book2 详述
   strcpy( Book2.title, "CSS 教程");
   strcpy( Book2.author, "Runoob");
   strcpy( Book2.subject, "前端技术");
   Book2.book_id = 12346;

   // 输出 Book1 信息
   cout << "第一本书标题 : " << Book1.title <<endl;
   cout << "第一本书作者 : " << Book1.author <<endl;
   cout << "第一本书类目 : " << Book1.subject <<endl;
   cout << "第一本书 ID : " << Book1.book_id <<endl;

   // 输出 Book2 信息
   cout << "第二本书标题 : " << Book2.title <<endl;
   cout << "第二本书作者 : " << Book2.author <<endl;
   cout << "第二本书类目 : " << Book2.subject <<endl;
   cout << "第二本书 ID : " << Book2.book_id <<endl;

   return 0;
}

输出:

第一本书标题 : C++ 教程
第一本书作者 : Runoob
第一本书类目 : 编程语言
第一本书 ID : 12345
第二本书标题 : CSS 教程
第二本书作者 : Runoob
第二本书类目 : 前端技术
第二本书 ID : 12346

结构作为函数参数

您可以把结构作为函数参数,传参方式与其他类型的变量或指针类似。您可以使用上面实例中的方式来访问结构
变量:

#include <iostream>
#include <cstring>

using namespace std;
void printBook( struct Books book );

// 声明一个结构体类型 Books 
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

int main( )
{
   Books Book1;        // 定义结构体类型 Books 的变量 Book1
   Books Book2;        // 定义结构体类型 Books 的变量 Book2

    // Book1 详述
   strcpy( Book1.title, "C++ 教程");
   strcpy( Book1.author, "Runoob"); 
   strcpy( Book1.subject, "编程语言");
   Book1.book_id = 12345;

   // Book2 详述
   strcpy( Book2.title, "CSS 教程");
   strcpy( Book2.author, "Runoob");
   strcpy( Book2.subject, "前端技术");
   Book2.book_id = 12346;

   // 输出 Book1 信息
   printBook( Book1 );

   // 输出 Book2 信息
   printBook( Book2 );

   return 0;
}
void printBook( struct Books book )
{
   cout << "书标题 : " << book.title <<endl;
   cout << "书作者 : " << book.author <<endl;
   cout << "书类目 : " << book.subject <<endl;
   cout << "书 ID : " << book.book_id <<endl;
}

指向结构的指针

您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:

#include <iostream>
#include <cstring>

using namespace std;
void printBook( struct Books *book );

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

int main( )
{
   Books Book1;        // 定义结构体类型 Books 的变量 Book1
   Books Book2;        // 定义结构体类型 Books 的变量 Book2

    // Book1 详述
   strcpy( Book1.title, "C++ 教程");
   strcpy( Book1.author, "Runoob"); 
   strcpy( Book1.subject, "编程语言");
   Book1.book_id = 12345;

   // Book2 详述
   strcpy( Book2.title, "CSS 教程");
   strcpy( Book2.author, "Runoob");
   strcpy( Book2.subject, "前端技术");
   Book2.book_id = 12346;

   // 通过传 Book1 的地址来输出 Book1 信息
   printBook( &Book1 );

   // 通过传 Book2 的地址来输出 Book2 信息
   printBook( &Book2 );

   return 0;
}
// 该函数以结构指针作为参数
void printBook( struct Books *book )
{
   cout << "书标题  : " << book->title <<endl;
   cout << "书作者 : " << book->author <<endl;
   cout << "书类目 : " << book->subject <<endl;
   cout << "书 ID : " << book->book_id <<endl;
}

输出:

书标题  : C++ 教程
书作者 : Runoob
书类目 : 编程语言
书 ID : 12345
书标题  : CSS 教程
书作者 : Runoob
书类目 : 前端技术
书 ID : 12346

typedef 关键字

下面是一种更简单的定义结构的方式,您可以为创建的类型取一个”别名”。例如:

typedef struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Books;

现在,您可以直接使用 Books 来定义 Books 类型的变量,而不需要使用 struct 关键字。下面是实例:

Books Book1, Book2;

面向对象

类&对象

C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。

C++ 类定义

class Box
{
   public:
      double length;   // 盒子的长度
      double breadth;  // 盒子的宽度
      double height;   // 盒子的高度
};

类成员数据

class Box
{
   public:
      double length;      // 长度
      double breadth;     // 宽度
      double height;      // 高度

      double getVolume(void)
      {
         return length * breadth * height;
      }
};

您也可以在类的外部使用 范围解析运算符:: 定义该函数,如下所示:

double Box::getVolume(void)
{
    return length * breadth * height;
}

类访问修饰符

数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 publicprivateprotected 来指定的。

  1. 公有(public)成员: 公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值
  2. 私有(private)成员: 私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
  3. 保护(protected)成员: 保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。

    继承中的特点

  • 1.public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
  • 2.protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
  • 3.private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private

但无论哪种继承方式,上面两点都没有改变:

  • 1.private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
  • 2.protected 成员可以被派生类访问。 ```cpp

    include

    include

    using namespace std;

class A{ public: int a; A(){ a1 = 1; a2 = 2; a3 = 3; a = 4; } void fun(){ cout << a << endl; //正确 cout << a1 << endl; //正确 cout << a2 << endl; //正确 cout << a3 << endl; //正确 } public: int a1; protected: int a2; private: int a3; }; class B : public A{ public: int a; B(int i){ A(); a = i; } void fun(){ cout << a << endl; //正确,public成员 cout << a1 << endl; //正确,基类的public成员,在派生类中仍是public成员。 cout << a2 << endl; //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。 cout << a3 << endl; //错误,基类的private成员不能被派生类访问。 } }; int main(){ B b(10); cout << b.a << endl; cout << b.a1 << endl; //正确 cout << b.a2 << endl; //错误,类外不能访问protected成员 cout << b.a3 << endl; //错误,类外不能访问private成员 system(“pause”); return 0; }

<a name="tfCcw"></a>
#### [构造函数 & 析构函数](https://www.runoob.com/cplusplus/cpp-constructor-destructor.html)
类的**构造函数**是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
```cpp
#include <iostream>

using namespace std;

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();  // 这是构造函数

   private:
      double length;
};

// 成员函数定义,包括构造函数
Line::Line(void)
{
    cout << "Object is being created" << endl;
}

void Line::setLength( double len )
{
    length = len;
}

double Line::getLength( void )
{
    return length;
}
// 程序的主函数
int main( )
{
   Line line;

   // 设置长度
   line.setLength(6.0); 
   cout << "Length of line : " << line.getLength() <<endl;

   return 0;
}

编译输出:

Object is being created
Length of line : 6

1. 使用初始化列表来初始化字段

使用初始化列表来初始化字段:

Line::Line( double len): length(len)
{
    cout << "Object is being created, length = " << len << endl;
}

上面的语法等同于如下语法:

Line::Line( double len)
{
    length = len;
    cout << "Object is being created, length = " << len << endl;
}

假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,您可以使用上面的语法,只需要在不同的字段使用逗号进行分隔,如下所示:

C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
  ....
}

2. 类的析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

#include <iostream>

using namespace std;

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();   // 这是构造函数声明
      ~Line();  // 这是析构函数声明

   private:
      double length;
};

// 成员函数定义,包括构造函数
Line::Line(void)
{
    cout << "Object is being created" << endl;
}
Line::~Line(void)
{
    cout << "Object is being deleted" << endl;
}

void Line::setLength( double len )
{
    length = len;
}

double Line::getLength( void )
{
    return length;
}
// 程序的主函数
int main( )
{
   Line line;

   // 设置长度
   line.setLength(6.0); 
   cout << "Length of line : " << line.getLength() <<endl;

   return 0;
}

编译输出:

Object is being created
Length of line : 6
Object is being deleted

拷贝析构函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象。
  • 复制对象把它作为参数传递给函数。
  • 复制对象,并从函数返回这个对象。
    // 在这里,obj 是一个对象引用,该对象是用于初始化另一个对象的。
    classname (const classname &obj) {
     // 构造函数的主体
    }
    
    ```cpp

    include

using namespace std;

class Line { public: int getLength( void ); Line( int len ); // 简单的构造函数 Line( const Line &obj); // 拷贝构造函数 ~Line(); // 析构函数

private: int *ptr; };

// 成员函数定义,包括构造函数 Line::Line(int len) { cout << “调用构造函数” << endl; // 为指针分配内存 ptr = new int; *ptr = len; }

Line::Line(const Line &obj) { cout << “调用拷贝构造函数并为指针 ptr 分配内存” << endl; ptr = new int; ptr = obj.ptr; // 拷贝值 }

Line::~Line(void) { cout << “释放内存” << endl; delete ptr; } int Line::getLength( void ) { return *ptr; }

void display(Line obj) { cout << “line 大小 : “ << obj.getLength() <<endl; }

// 程序的主函数 int main( ) { Line line1(10);

Line line2 = line1; // 这里也调用了拷贝构造函数

display(line1); display(line2);

return 0; }

当上面的代码被编译和执行时,它会产生下列结果:
```cpp
调用构造函数
调用拷贝构造函数并为指针 ptr 分配内存
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
释放内存
释放内存

类的静态成员

我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

#include <iostream>

using namespace std;

class Box
{
   public:
      static int objectCount;
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         cout <<"Constructor called." << endl;
         length = l;
         breadth = b;
         height = h;
         // 每次创建对象时增加 1
         objectCount++;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      static int getCount()
      {
         return objectCount;
      }
   private:
      double length;     // 长度
      double breadth;    // 宽度
      double height;     // 高度
};

// 初始化类 Box 的静态成员
int Box::objectCount = 0;

int main(void)
{

   // 在创建对象之前输出对象的总数
   cout << "Inital Stage Count: " << Box::getCount() << endl;

   Box Box1(3.3, 1.2, 1.5);    // 声明 box1
   Box Box2(8.5, 6.0, 2.0);    // 声明 box2

   // 在创建对象之后输出对象的总数
   cout << "Final Stage Count: " << Box::getCount() << endl;

   return 0;
}

执行输出:

Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2

高级教程

命名空间

假设这样一种情况,当一个班上有两个名叫 Zara 的学生时,为了明确区分它们,我们在使用名字之外,不得不使用一些额外的信息,比如他们的家庭住址,或者他们父母的名字等等。
同样的情况也出现在 C++ 应用程序中。例如,您可能会写一个名为 xyz() 的函数,在另一个可用的库中也存在一个相同的函数 xyz()。这样,编译器就无法判断您所使用的是哪一个 xyz() 函数。
因此,引入了命名空间这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。

定义命名空间

命名空间的定义使用关键字 namespace,后跟命名空间的名称,如下所示:

namespace namespace_name {
   // 代码声明
}

为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称,如下所示:

name::code;  // code 可以是变量或函数
#include <iostream>
using namespace std;

// 第一个命名空间
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
}
// 第二个命名空间
namespace second_space{
   void func(){
      cout << "Inside second_space" << endl;
   }
}
int main ()
{

   // 调用第一个命名空间中的函数
   first_space::func();

   // 调用第二个命名空间中的函数
   second_space::func(); 

   return 0;
}

编码输出:

Inside first_space
Inside second_space

using 指令

您可以使用 **using namespace** 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。

#include <iostream>
using namespace std;

// 第一个命名空间
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
}
// 第二个命名空间
namespace second_space{
   void func(){
      cout << "Inside second_space" << endl;
   }
}

using namespace first_space;
int main ()
{

   // 调用第一个命名空间中的函数
   func();

   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Inside first_space

using 指令也可以用来指定命名空间中的特定项目。例如,如果您只打算使用 std 命名空间中的 cout 部分,您可以使用如下的语句:

using std::cout;

随后的代码中,在使用 cout 时就可以不用加上命名空间名称作为前缀,但是 std 命名空间中的其他项目仍然需要加上命名空间名称作为前缀,如下所示:

#include <iostream>
using std::cout;

int main ()
{

   cout << "std::endl is used with std!" << std::endl;

   return 0;
}

// output:
// std::endl is used with std!

using 指令引入的名称遵循正常的范围规则。名称从使用 using 指令开始是可见的,直到该范围结束。此时,在范围以外定义的同名实体是隐藏的。

嵌套的命名空间

命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间,您可以通过使用 :: 运算符来访问嵌套的命名空间中的成员:

// 访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2;

// 访问 namespace:name1 中的成员
using namespace namespace_name1;

在上面的语句中,如果使用的是 namespace_name1,那么在该范围内 namespace_name2 中的元素也是可用的,如下所示:

#include <iostream>
using namespace std;

// 第一个命名空间
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
   // 第二个命名空间
   namespace second_space{
      void func(){
         cout << "Inside second_space" << endl;
      }
   }
}
using namespace first_space::second_space;
int main ()
{

   // 调用第二个命名空间中的函数
   func();

   return 0;
}

// output:
// Inside second_space

关于命名空间内变量和函数及全局变量的使用和作用域:

#include <iostream>
using namespace std;
namespace A
{
    int a = 100;
    namespace B            //嵌套一个命名空间B
    {
        int a =20;
    }
}

int a = 200;//定义一个全局变量


int main(int argc, char *argv[])
{
    cout <<"A::a ="<< A::a << endl;
    cout <<"A::B::a ="<<A::B::a << endl;
    cout <<"a ="<<a << endl;
    cout <<"::a ="<<::a << endl;

    int a = 30;
    cout <<"a ="<<a << endl;
    cout <<"::a ="<<::a << endl;

    return 0;
}

输出:

A::a =100  
A::B::a =20
a =200      //全局变量a
::a =200
a =30       //局部变量a
::a =200

:全局变量 a 表达为 ::a,用于当有同名的局部变量时来区别两者。

动态内存

C++ 程序中的内存分为两个部分:

  • 栈:在函数内部声明的所有变量都将占用栈内存。
  • 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。

很多时候,您无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。
在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。
如果您不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。
下面的实例中使用了上面的概念,演示了如何使用 new 和 delete 运算符:

#include <iostream>
using namespace std;

int main ()
{
   double* pvalue  = NULL; // 初始化为 null 的指针
   pvalue  = new double;   // 为变量请求内存

   *pvalue = 29494.99;     // 在分配的地址存储值
   cout << "Value of pvalue : " << *pvalue << endl;

   delete pvalue;         // 释放内存

   return 0;
}

模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。
模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。
每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector vector
您可以使用模板来定义函数和类,接下来让我们一起来看看如何使用。

函数模板

template <typename type> ret-type func-name(parameter list)
{
   // 函数的主体
}

句柄(handle)

通俗的讲,c++句柄就像一个“复合指针”:它不但“指向”某个对象,还“记录”所指对象由于复制/赋值而被其他句柄拥有的次数。当没有句柄与该对象关联的时候,他还负责删除该对象。
句柄的英文是 handle。在英文中,有操作、处理、控制之类的意义。作为一个名词时,是指某个中间媒介,通过这个中间媒介可控制、操作某样东西。
这样说有点抽象,举个例子。door handle 是指门把手,通过门把手可以去控制门,但 door handle 并非 door 本身,只是一个中间媒介。又比如 knife handle 是刀柄,通过刀柄可以使用刀。

句柄类

泛型句柄类模板:

template<typename T>
class Handel{
public:
    Handle(T *p = 0):ptr(p),use(new size_t(1)){}
    T& operator*(){
        if(ptr)
            return *ptr;
    }
    T* operator->(){
        if(ptr)
            return ptr;
    }
    Handle(const Handle &h):ptr(h.ptr),use(h.use){++*use}
    Handle& operator=(const Handle &h){
        ++*use;
        del();
        ptr = h.ptr;
        use = h.use;
        return *this;
    }
    ~Handel(){del();}
private:
    T* ptr;
    std::size_t *use;
    void del(){
        if(--*use == 0)
        {
            delete ptr; delete use;
        }
    }
};

句柄实际上是一个类,之所以说他像一个指针,因为他重载了指针的解引用操作符和成员访问操作符;

ptr 指向句柄所要管理的对象,use指向次对象被其他句柄关联的次数,当没有其他句柄关联次对象的时候通过del()函数删除该对象。

使用句柄的好处是:用户在某些地方需要用指针操作对象时(比如把书(父类对象)和打折的书(派生类对象)加入同一个购物车Vector中),

用句柄而不用指针,可以无需担心指针的管理(e,什么时候该删除对象,指针删除的时候?还有别的指针指向次对象呢?),同时也可以获得动态绑定的行为。因为,句柄可以根据use来判断什么时候该删除对象。

参考
【泛型句柄类】:https://blog.csdn.net/kee131/article/details/44592285

宏/预处理器

预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。

我们已经看到,之前所有的实例中都有 #include 指令。这个宏用于把头文件包含到源文件中。
C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令。

define 预处理

define 预处理指令用于创建符号常量。该符号常量通常称为,指令的一般形式是:

#define macro-name replacement-text

当这一行代码出现在一个文件中时,在该文件中后续出现的所有宏都将会在程序编译之前被替换为 replacement-text。例如:

#include <iostream>
using namespace std;

#define PI 3.14159

int main ()
{

    cout << "Value of PI :" << PI << endl; 

    return 0;
}

参数宏

您可以使用 #define 来定义一个带有参数的宏,如下所示:

#include <iostream>
using namespace std;

#define MIN(a,b) (a<b ? a : b)

int main ()
{
   int i, j;
   i = 100;
   j = 30;
   cout <<"较小的值为:" << MIN(i, j) << endl;

    return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

较小的值为:30

条件编译

有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。
条件预处理器的结构与 if 选择结构很像。请看下面这段预处理器的代码:

#ifdef NULL
   #define NULL 0
#endif

您可以只在调试时进行编译,调试开关可以使用一个宏来实现,如下所示:

#ifdef DEBUG
   cerr <<"Variable x = " << x << endl;
#endif

如果在指令 #ifdef DEBUG 之前已经定义了符号常量 DEBUG,则会对程序中的 cerr 语句进行编译。您可以使用 #if 0 语句注释掉程序的一部分,如下所示:

#if 0
   不进行编译的代码
#endif