C++ 快速指南



C++ 概述

C++ 是一种静态类型的、编译型的、通用的、区分大小写的、自由格式的编程语言,支持过程式编程、面向对象编程和泛型编程。

C++ 被认为是一种中级语言,因为它结合了高级和低级语言的特性。

C++ 由 Bjarne Stroustrup 于 1979 年在位于新泽西州默里山的贝尔实验室开始开发,作为对 C 语言的增强,最初命名为“带类的 C”,后来于 1983 年改名为 C++。

C++ 是 C 的超集,几乎任何合法的 C 程序都是合法的 C++ 程序。

注意 − 当类型检查在编译时而不是运行时执行时,据说编程语言使用静态类型。

面向对象编程

C++ 完全支持面向对象编程,包括面向对象开发的四个支柱:

  • 封装
  • 数据隐藏
  • 继承
  • 多态性

标准库

标准 C++ 包含三个重要的部分:

  • 核心语言,提供所有构建块,包括变量、数据类型和字面量等。

  • C++ 标准库,提供丰富的函数来操作文件、字符串等。

  • 标准模板库 (STL),提供丰富的用于操作数据结构的方法等。

ANSI 标准

ANSI 标准旨在确保 C++ 的可移植性;也就是说,你为 Microsoft 编译器编写的代码将在 Mac、UNIX、Windows 或 Alpha 上的编译器上无需错误地进行编译。

ANSI 标准已经稳定了一段时间,所有主要的 C++ 编译器制造商都支持 ANSI 标准。

学习 C++

学习 C++ 最重要的事情是关注概念。

学习编程语言的目的是成为更好的程序员;也就是说,更有效地设计和实现新系统以及维护旧系统。

C++ 支持各种编程风格。你可以在任何语言中使用 Fortran、C、Smalltalk 等的风格进行编写。每种风格都能有效地实现其目标,同时保持运行时和空间效率。

C++ 的用途

C++ 被数十万程序员用于几乎所有应用领域。

C++ 被广泛用于编写设备驱动程序和其他依赖于在实时约束下直接操纵硬件的软件。

C++ 广泛用于教学和研究,因为它足够清晰,可以成功地教授基本概念。

任何使用过 Apple Macintosh 或运行 Windows 的 PC 的人都间接地使用了 C++,因为这些系统的主要用户界面是用 C++ 编写的。

C++ 环境设置

本地环境设置

如果你仍然希望为 C++ 设置你的环境,你需要在你的计算机上安装以下两个软件。

文本编辑器

这将用于键入你的程序。一些编辑器的例子包括 Windows 记事本、OS Edit 命令、Brief、Epsilon、EMACS 和 vim 或 vi。

文本编辑器的名称和版本在不同的操作系统上可能会有所不同。例如,Windows 上将使用记事本,而 vim 或 vi 可以在 Windows、Linux 或 UNIX 上使用。

你用编辑器创建的文件称为源文件,对于 C++,它们通常以 .cpp、.cp 或 .c 扩展名命名。

为了开始你的 C++ 编程,必须有一个文本编辑器。

C++ 编译器

这是一个实际的 C++ 编译器,它将用于将你的源代码编译成最终的可执行程序。

大多数 C++ 编译器并不关心你给源代码赋予什么扩展名,但如果你没有另外指定,许多编译器默认使用 .cpp。

最常用且免费提供的编译器是 GNU C/C++ 编译器,或者如果你有相应的操作系统,你可以使用 HP 或 Solaris 的编译器。

安装 GNU C/C++ 编译器

UNIX/Linux 安装

如果你使用的是Linux 或 UNIX,请通过从命令行输入以下命令来检查你的系统上是否安装了 GCC:

$ g++ -v

如果你安装了 GCC,它应该打印类似以下的消息:

Using built-in specs.
Target: i386-redhat-linux
Configured with: ../configure --prefix=/usr .......
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-46)

如果未安装 GCC,则需要使用https://gcc.gnu.org/install/提供的详细说明自行安装。

Mac OS X 安装

如果你使用 Mac OS X,获取 GCC 的最简单方法是从 Apple 的网站下载 Xcode 开发环境,并按照简单的安装说明进行操作。

Xcode 目前可在developer.apple.com/technologies/tools/获取。

Windows 安装

要在 Windows 上安装 GCC,你需要安装 MinGW。要安装 MinGW,请访问 MinGW 主页 www.mingw.org,然后点击链接进入 MinGW 下载页面。下载最新版本的 MinGW 安装程序,其名称应为 MinGW-<version>.exe。

安装 MinGW 时,至少必须安装 gcc-core、gcc-g++、binutils 和 MinGW 运行时,但你可能希望安装更多。

将 MinGW 安装的 bin 子目录添加到你的PATH环境变量中,以便你可以通过简单的名称在命令行中指定这些工具。

安装完成后,你将能够从 Windows 命令行运行 gcc、g++、ar、ranlib、dlltool 和其他几个 GNU 工具。

C++ 基本语法

当我们考虑一个 C++ 程序时,它可以定义为通过调用彼此的方法进行通信的对象的集合。现在让我们简要地了解一下类、对象、方法和实例变量的含义。

  • 对象 − 对象具有状态和行为。例如:一只狗具有状态——颜色、名字、品种以及行为——摇尾、吠叫、吃东西。对象是类的实例。

  • − 类可以定义为一个模板/蓝图,它描述了其类型对象支持的行为/状态。

  • 方法 − 方法基本上是一种行为。一个类可以包含许多方法。在方法中编写逻辑、操作数据和执行所有操作。

  • 实例变量 − 每个对象都有自己唯一的一组实例变量。对象的 state 是通过赋予这些实例变量的值来创建的。

C++ 程序结构

让我们来看一个简单的代码,它将打印单词“Hello World”。

#include <iostream>
using namespace std;

// main() is where program execution begins.
int main() {
   cout << "Hello World"; // prints Hello World
   return 0;
}

让我们看看上面程序的各个部分:

  • C++ 语言定义了几个头文件,其中包含对你的程序来说是必需的或有用的信息。对于此程序,需要头文件<iostream>

  • using namespace std; 这行代码告诉编译器使用 std 命名空间。命名空间是 C++ 中相对较新的添加。

  • 下一行 '// main() is where program execution begins.' 是 C++ 中提供的单行注释。单行注释以 // 开头,在行尾结束。

  • int main() 这行代码是主函数,程序执行从此处开始。

  • 下一行cout << "Hello World"; 将消息“Hello World”显示在屏幕上。

  • 下一行return 0; 终止 main() 函数并使其向调用进程返回值 0。

编译和执行 C++ 程序

让我们看看如何保存文件、编译和运行程序。请按照以下步骤操作:

  • 打开文本编辑器并添加上述代码。

  • 将文件保存为:hello.cpp

  • 打开命令提示符并转到保存文件的目录。

  • 键入“g++ hello.cpp”并按 Enter 键编译你的代码。如果你的代码中没有错误,命令提示符将带你到下一行,并生成 a.out 可执行文件。

  • 现在,键入“a.out”运行你的程序。

  • 你将看到窗口上打印“Hello World”。

$ g++ hello.cpp
$ ./a.out
Hello World

确保 g++ 位于你的路径中,并且你正在包含文件 hello.cpp 的目录中运行它。

你可以使用 makefile 编译 C/C++ 程序。更多详情,请查看我们的“Makefile 教程”

C++ 中的分号和块

在 C++ 中,分号是语句终止符。也就是说,每个单独的语句都必须以分号结尾。它表示一个逻辑实体的结束。

例如,以下是三个不同的语句:

x = y;
y = y + 1;
add(x, y);

块是一组逻辑上连接的语句,它们被大括号包围。例如:

{
   cout << "Hello World"; // prints Hello World
   return 0;
}

C++ 不识别行尾作为终止符。因此,你在一行中放置语句的位置无关紧要。例如:

x = y;
y = y + 1;
add(x, y);

与以下相同

x = y; y = y + 1; add(x, y);

C++ 标识符

C++标识符是用于标识变量、函数、类、模块或任何其他用户定义项的名称。标识符以字母A到Z或a到z或下划线(_)开头,后跟零个或多个字母、下划线和数字(0到9)。

C++不允许在标识符中使用@、$和%等标点符号。C++是一种区分大小写的编程语言。因此,Manpowermanpower在C++中是两个不同的标识符。

以下是一些可接受的标识符示例:

mohd       zara    abc   move_name  a_123
myname50   _temp   j     a23b9      retVal

C++ 关键字

以下列表显示了C++中的保留字。这些保留字不能用作常量或变量或任何其他标识符名称。

asm else new this
auto enum operator throw
bool explicit private true
break export protected try
case extern public typedef
catch false register typeid
char float reinterpret_cast typename
class for return union
const friend short unsigned
const_cast goto signed using
continue if sizeof virtual
default inline static void
delete int static_cast volatile
do long struct wchar_t
double mutable switch while
dynamic_cast namespace template  

三元组

一些字符具有另一种表示形式,称为三元组序列。三元组是一个三字符序列,代表单个字符,该序列总是以两个问号开头。

三元组在其出现的任何位置都被扩展,包括字符串文字和字符文字、注释和预处理器指令中。

以下是最常用的三元组序列:

三元组 替换字符
??= #
??/ \
??' ^
??( [
??) ]
??! |
??< {
??> }
??- ~

并非所有编译器都支持三元组,并且由于其混淆性,建议不要使用它们。

C++中的空白字符

仅包含空白字符(可能带有注释)的行称为空行,C++编译器会完全忽略它。

空白字符是C++中用来描述空格、制表符、换行符和注释的术语。空白字符将语句的一个部分与另一个部分分开,并使编译器能够识别语句中一个元素(例如int)的结束位置和下一个元素的开始位置。

语句 1

int age;

在上述语句中,int和age之间必须至少有一个空白字符(通常是空格),才能使编译器能够区分它们。

语句 2

fruit = apples + oranges;   // Get the total fruit

在上述语句2中,fruit和=之间,或=和apples之间不需要空白字符,尽管您可以根据可读性需要包含一些空白字符。

C++中的注释

程序注释是可以包含在C++代码中的解释性语句。这些注释有助于任何阅读源代码的人。所有编程语言都允许某种形式的注释。

C++支持单行和多行注释。C++编译器会忽略任何注释中包含的所有字符。

C++注释以/*开头,以*/结尾。例如:

/* This is a comment */

/* C++ comments can also
   * span multiple lines
*/

注释也可以以//开头,一直延伸到行尾。例如:

#include <iostream>
using namespace std;

main() {
   cout << "Hello World"; // prints Hello World
   
   return 0;
}

编译上述代码时,它将忽略// prints Hello World,最终的可执行文件将产生以下结果:

Hello World

在/*和*/注释中,//字符没有任何特殊含义。在//注释中,/*和*/没有任何特殊含义。因此,您可以将一种注释“嵌套”在另一种注释中。例如:

/* Comment out printing of Hello World:

cout << "Hello World"; // prints Hello World

*/

C++ 数据类型

在任何语言中编写程序时,都需要使用各种变量来存储各种信息。变量只不过是保留的内存位置,用于存储值。这意味着当您创建变量时,您会在内存中保留一些空间。

您可能希望存储各种数据类型的信息,例如字符、宽字符、整数、浮点数、双精度浮点数、布尔值等。根据变量的数据类型,操作系统分配内存并决定可以在保留的内存中存储什么。

基本内置类型

C++为程序员提供了丰富的内置和用户定义数据类型。下表列出了七种基本C++数据类型:

类型 关键字
布尔型 bool
字符型 char
整型 int
浮点型 float
双精度浮点型 double
空值型 void
宽字符型 wchar_t

可以使用一个或多个类型修饰符修改几种基本类型:

  • signed
  • unsigned
  • short
  • long

下表显示了变量类型、存储值所需的内存大小以及可以在此类变量中存储的最大值和最小值。

类型 典型位宽 典型范围
char 1字节 -127到127或0到255
unsigned char 1字节 0到255
signed char 1字节 -127到127
int 4字节 -2147483648到2147483647
unsigned int 4字节 0到4294967295
signed int 4字节 -2147483648到2147483647
short int 2字节 -32768到32767
unsigned short int 2字节 0到65,535
signed short int 2字节 -32768到32767
long int 8字节 -2,147,483,648到2,147,483,647
signed long int 8字节 与long int相同
unsigned long int 8字节 0到4,294,967,295
long long int 8字节 -(2^63)到(2^63)-1
unsigned long long int 8字节 0到18,446,744,073,709,551,615
float 4字节
double 8字节
long double 12字节
wchar_t 2或4字节 1个宽字符

变量的大小可能与上表中显示的大小不同,具体取决于您使用的编译器和计算机。

以下是示例,它将生成您计算机上各种数据类型的正确大小。

#include <iostream>
using namespace std;

int main() {
   cout << "Size of char : " << sizeof(char) << endl;
   cout << "Size of int : " << sizeof(int) << endl;
   cout << "Size of short int : " << sizeof(short int) << endl;
   cout << "Size of long int : " << sizeof(long int) << endl;
   cout << "Size of float : " << sizeof(float) << endl;
   cout << "Size of double : " << sizeof(double) << endl;
   cout << "Size of wchar_t : " << sizeof(wchar_t) << endl;
   
   return 0;
}

此示例使用endl,它在每一行之后插入一个换行符,并使用<<运算符将多个值输出到屏幕。我们还使用sizeof()运算符来获取各种数据类型的大小。

编译并执行上述代码后,将产生以下结果,该结果可能因机器而异:

Size of char : 1
Size of int : 4
Size of short int : 2
Size of long int : 4
Size of float : 4
Size of double : 8
Size of wchar_t : 4

typedef声明

您可以使用typedef为现有类型创建一个新名称。以下是使用typedef定义新类型的简单语法:

typedef type newname; 

例如,以下代码告诉编译器feet是int的另一个名称:

typedef int feet;

现在,以下声明是完全合法的,并创建一个名为distance的整型变量:

feet distance;

枚举类型

枚举类型声明一个可选的类型名称和一组零个或多个可作为类型值的标识符。每个枚举器都是一个常量,其类型为枚举。

创建枚举需要使用关键字enum。枚举类型的通用形式如下:

enum enum-name { list of names } var-list; 

这里,enum-name是枚举的类型名称。名称列表用逗号分隔。

例如,以下代码定义了一个名为colors的颜色的枚举和color类型的变量c。最后,将c赋值为“blue”。

enum color { red, green, blue } c;
c = blue;

默认情况下,第一个名称的值为0,第二个名称的值为1,第三个名称的值为2,依此类推。但是,您可以通过添加初始化程序为名称指定特定值。例如,在以下枚举中,green的值将为5。

enum color { red, green = 5, blue };

这里,blue的值将为6,因为每个名称都比其前面的名称大1。

C++ 变量类型

变量为我们提供了程序可以操作的命名存储。C++中的每个变量都有一个特定类型,该类型决定变量内存的大小和布局;可以存储在该内存中的值的范围;以及可以应用于变量的操作集。

变量的名称可以由字母、数字和下划线组成。它必须以字母或下划线开头。大写字母和小写字母是不同的,因为C++区分大小写:

C++中存在以下基本类型的变量,如上一章所述:

序号 类型和描述
1

bool

存储true或false值。

2

char

通常是一个八位字节(一个字节)。这是一个整型。

3

int

机器中最自然的整数大小。

4

float

单精度浮点值。

5

double

双精度浮点值。

6

void

表示类型的缺失。

7

wchar_t

宽字符类型。

C++还允许定义各种其他类型的变量,我们将在后续章节中介绍,例如枚举、指针、数组、引用、数据结构

下一节将介绍如何定义、声明和使用各种类型的变量。

C++中的变量定义

变量定义告诉编译器在哪里以及为变量创建多少存储空间。变量定义指定数据类型,并包含一个或多个该类型变量的列表,如下所示:

type variable_list;

这里,type必须是有效的C++数据类型,包括char、w_char、int、float、double、bool或任何用户定义的对象等,而variable_list可以包含一个或多个用逗号分隔的标识符名称。这里显示了一些有效的声明:

int    i, j, k;
char   c, ch;
float  f, salary;
double d;

int i, j, k;这一行声明并定义了变量i、j和k;它指示编译器创建名为i、j和k的int类型变量。

可以在变量声明中初始化(赋值初始值)变量。初始化程序由等号后跟一个常量表达式组成,如下所示:

type variable_name = value;

一些例子:

extern int d = 3, f = 5;    // declaration of d and f. 
int d = 3, f = 5;           // definition and initializing d and f. 
byte z = 22;                // definition and initializes z. 
char x = 'x';               // the variable x has the value 'x'.

对于没有初始化器的定义:具有静态存储期的变量会隐式初始化为 NULL(所有字节的值都为 0);所有其他变量的初始值未定义。

C++ 中的变量声明

变量声明向编译器保证存在一个具有给定类型和名称的变量,以便编译器继续进行编译,而无需关于变量的完整细节。变量声明只在编译时有意义,编译器在程序链接时需要实际的变量定义。

当您使用多个文件并将变量定义在一个文件中(该文件将在程序链接时可用)时,变量声明非常有用。您将使用extern关键字在任何地方声明变量。尽管您可以在 C++ 程序中多次声明变量,但它只能在一个文件中、一个函数中或一段代码块中定义一次。

示例

尝试以下示例,其中变量已在顶部声明,但已在 main 函数内定义:

#include <iostream>
using namespace std;

// Variable declaration:
extern int a, b;
extern int c;
extern float f;
  
int main () {
   // Variable definition:
   int a, b;
   int c;
   float f;
 
   // actual initialization
   a = 10;
   b = 20;
   c = a + b;
 
   cout << c << endl ;

   f = 70.0/3.0;
   cout << f << endl ;
 
   return 0;
}

编译并执行上述代码时,会产生以下结果:

30
23.3333

相同的概念也适用于函数声明,您在函数声明时提供函数名称,其实际定义可以在其他任何地方给出。例如:

// function declaration
int func();
int main() {
   // function call
   int i = func();
}

// function definition
int func() {
   return 0;
}

左值和右值

C++ 中有两种表达式:

  • 左值 - 指向内存位置的表达式称为“左值”表达式。左值可以作为赋值的左侧或右侧出现。

  • 右值 - 右值指的是存储在内存某个地址处的数据值。右值是一个不能为其赋值的表达式,这意味着右值可以出现在赋值的右侧,但不能出现在左侧。

变量是左值,因此可以出现在赋值的左侧。数字字面值是右值,因此不能赋值,也不能出现在左侧。以下是有效的语句:

int g = 20;

但以下语句无效,会产生编译时错误:

10 = 20;

C++ 中的变量作用域

作用域是程序的一个区域,广义地说,变量可以在三个地方声明:

  • 在函数或代码块内,称为局部变量;

  • 在函数参数定义中,称为形式参数;

  • 在所有函数之外,称为全局变量;

我们将在后续章节中学习什么是函数及其参数。这里让我们解释一下局部变量和全局变量。

局部变量

在函数或代码块内部声明的变量是局部变量。只有函数或代码块内部的语句才能使用它们。外部函数不知道局部变量。以下是使用局部变量的示例:

#include <iostream>
using namespace std;
 
int main () {
   // Local variable declaration:
   int a, b;
   int c;
 
   // actual initialization
   a = 10;
   b = 20;
   c = a + b;
 
   cout << c;
 
   return 0;
}

全局变量

全局变量定义在所有函数之外,通常位于程序顶部。全局变量将在程序的整个生命周期内保持其值。

任何函数都可以访问全局变量。也就是说,全局变量在其声明后可在整个程序中使用。以下是使用全局变量和局部变量的示例:

#include <iostream>
using namespace std;
 
// Global variable declaration:
int g;
 
int main () {
   // Local variable declaration:
   int a, b;
 
   // actual initialization
   a = 10;
   b = 20;
   g = a + b;
  
   cout << g;
 
   return 0;
}

程序可以为局部变量和全局变量使用相同的名称,但函数内部局部变量的值将优先。例如:

#include <iostream>
using namespace std;
 
// Global variable declaration:
int g = 20;
 
int main () {
   // Local variable declaration:
   int g = 10;
 
   cout << g;
 
   return 0;
}

编译并执行上述代码时,会产生以下结果:

10

局部变量和全局变量的初始化

定义局部变量时,系统不会对其进行初始化,您必须自行初始化。全局变量在您按如下方式定义它们时会由系统自动初始化:

数据类型 初始化器
int 0
char '\0'
float 0
double 0
指针 NULL

正确初始化变量是一个良好的编程习惯,否则有时程序会产生意想不到的结果。

C++ 常量/字面量

常量是指程序可能不会更改的固定值,它们称为字面量

常量可以是任何基本数据类型,可以分为整数字面量、浮点字面量、字符、字符串和布尔值。

同样,常量与普通变量一样,只是它们的值在定义后不能修改。

整数字面量

整数字面量可以是十进制、八进制或十六进制常量。前缀指定基数或基:十六进制为 0x 或 0X,八进制为 0,十进制为无。

整数字面量还可以带后缀,该后缀是 U 和 L 的组合,分别表示无符号和长整型。后缀可以是大写或小写,并且可以按任意顺序排列。

以下是一些整数字面量的示例:

212         // Legal
215u        // Legal
0xFeeL      // Legal
078         // Illegal: 8 is not an octal digit
032UU       // Illegal: cannot repeat a suffix

以下是各种类型整数字面量的其他示例:

85         // decimal
0213       // octal
0x4b       // hexadecimal
30         // int
30u        // unsigned int
30l        // long
30ul       // unsigned long

浮点字面量

浮点字面量具有整数部分、小数点、小数部分和指数部分。您可以使用十进制形式或指数形式表示浮点字面量。

使用十进制形式表示时,必须包含小数点、指数或两者兼有;使用指数形式表示时,必须包含整数部分、小数部分或两者兼有。带符号的指数由 e 或 E 引入。

以下是一些浮点字面量的示例:

3.14159       // Legal
314159E-5L    // Legal
510E          // Illegal: incomplete exponent
210f          // Illegal: no decimal or exponent
.e55          // Illegal: missing integer or fraction

布尔字面量

有两个布尔字面量,它们是标准 C++ 关键字的一部分:

  • 值为true表示真。

  • 值为false表示假。

不应将 true 的值视为 1,将 false 的值视为 0。

字符字面量

字符字面量用单引号括起来。如果字面量以 L(仅大写)开头,则它是宽字符字面量(例如,L'x'),应存储在wchar_t类型的变量中。否则,它是窄字符字面量(例如,'x'),可以存储在char类型的简单变量中。

字符字面量可以是普通字符(例如,'x')、转义序列(例如,'\t')或通用字符(例如,'\u02C0')。

在 C++ 中,某些字符在前面加上反斜杠时具有特殊含义,它们用于表示换行符(\n)或制表符(\t)。这里列出了一些这样的转义序列代码:

转义序列 含义
\\ \ 字符
\' ' 字符
\" " 字符
\? ? 字符
\a 警告或铃声
\b 退格
\f 换页
\n 换行
\r 回车
\t 水平制表符
\v 垂直制表符
\ooo 一位到三位八进制数
\xhh . . . 一位或多位十六进制数

以下示例显示了一些转义序列字符:

#include <iostream>
using namespace std;

int main() {
   cout << "Hello\tWorld\n\n";
   return 0;
}

编译并执行上述代码时,会产生以下结果:

Hello   World

字符串字面量

字符串字面量用双引号括起来。字符串包含与字符字面量相似的字符:普通字符、转义序列和通用字符。

您可以使用字符串字面量将长行分成多行,并使用空格分隔它们。

以下是一些字符串字面量的示例。所有三种形式都是相同的字符串。

"hello, dear"

"hello, \

dear"

"hello, " "d" "ear"

定义常量

在 C++ 中定义常量有两种简单的方法:

  • 使用#define预处理器。

  • 使用const关键字。

#define 预处理器

以下是使用 #define 预处理器定义常量的形式:

#define identifier value

以下示例详细解释了它:

#include <iostream>
using namespace std;

#define LENGTH 10   
#define WIDTH  5
#define NEWLINE '\n'

int main() {
   int area;  
   
   area = LENGTH * WIDTH;
   cout << area;
   cout << NEWLINE;
   return 0;
}

编译并执行上述代码时,会产生以下结果:

50

const 关键字

您可以使用const前缀以特定的类型声明常量,如下所示:

const type variable = value;

以下示例详细解释了它:

#include <iostream>
using namespace std;

int main() {
   const int  LENGTH = 10;
   const int  WIDTH  = 5;
   const char NEWLINE = '\n';
   int area;  
   
   area = LENGTH * WIDTH;
   cout << area;
   cout << NEWLINE;
   return 0;
}

编译并执行上述代码时,会产生以下结果:

50

请注意,良好的编程习惯是用大写字母定义常量。

C++ 修饰符类型

C++ 允许char、intdouble数据类型在其前面有修饰符。修饰符用于更改基本类型的含义,使其更精确地适应各种情况的需求。

数据类型修饰符列于此处:

  • signed
  • unsigned
  • long
  • short

修饰符signed、unsigned、longshort可以应用于整数基本类型。此外,signedunsigned可以应用于 char,long可以应用于 double。

修饰符signedunsigned也可以用作longshort修饰符的前缀。例如,unsigned long int

C++ 允许使用简写法声明unsigned、shortlong整数。您可以简单地使用unsigned、shortlong,而无需int。它会自动暗示int。例如,以下两个语句都声明了无符号整数变量。

unsigned x;
unsigned int y;

要了解 C++ 解释带符号和无符号整数修饰符的方式之间的区别,您应该运行以下简短程序:

#include <iostream>
using namespace std;
 
/* This program shows the difference between
   * signed and unsigned integers.
*/
int main() {
   short int i;           // a signed short integer
   short unsigned int j;  // an unsigned short integer

   j = 50000;

   i = j;
   cout << i << " " << j;

   return 0;
}

运行此程序时,输出如下:

-15536 50000

上述结果是由于表示 50,000 作为短无符号整数的位模式被 short 解释为 -15,536。

C++ 中的类型限定符

类型限定符提供有关它们前面的变量的附加信息。

序号 限定符和含义
1

const

程序在执行期间无法更改const类型的对象。

2

volatile

修饰符volatile告诉编译器变量的值可能会以程序未明确指定的方式更改。

3

restrict

restrict限定的指针最初是访问其指向的对象的唯一方法。只有 C99 添加了一个名为 restrict 的新类型限定符。

C++ 中的存储类

存储类定义 C++ 程序中变量和/或函数的作用域(可见性)和生命周期。这些说明符位于它们修改的类型之前。C++ 程序中可以使用以下存储类

  • auto
  • register
  • static
  • extern
  • mutable

auto 存储类

auto存储类是所有局部变量的默认存储类。

{
   int mount;
   auto int month;
}

上面的示例定义了两个具有相同存储类的变量,auto 只能在函数内使用,即局部变量。

register 存储类

register存储类用于定义应存储在寄存器而不是 RAM 中的局部变量。这意味着变量的最大大小等于寄存器大小(通常是一个字),并且不能对其应用一元 '&' 运算符(因为它没有内存位置)。

{
   register int  miles;
}

register 应该仅用于需要快速访问的变量,例如计数器。还应注意,定义 'register' 并不意味着变量将存储在寄存器中。这意味着它可能会存储在寄存器中,具体取决于硬件和实现限制。

static 存储类

static存储类指示编译器在程序的生命周期内保持局部变量的存在,而不是在每次进入和退出作用域时创建和销毁它。因此,使局部变量静态允许它们在函数调用之间保持其值。

static 修饰符也可以应用于全局变量。当这样做时,它会导致该变量的作用域限制在其声明的文件中。

在 C++ 中,当在类数据成员上使用 static 时,它只导致一个副本由其类的所有对象共享。

#include <iostream>
 
// Function declaration
void func(void);
 
static int count = 10; /* Global variable */
 
main() {
   while(count--) {
      func();
   }
   
   return 0;
}

// Function definition
void func( void ) {
   static int i = 5; // local static variable
   i++;
   std::cout << "i is " << i ;
   std::cout << " and count is " << count << std::endl;
}

编译并执行上述代码时,会产生以下结果:

i is 6 and count is 9
i is 7 and count is 8
i is 8 and count is 7
i is 9 and count is 6
i is 10 and count is 5
i is 11 and count is 4
i is 12 and count is 3
i is 13 and count is 2
i is 14 and count is 1
i is 15 and count is 0

extern 存储类

extern存储类用于引用对所有程序文件可见的全局变量。当您使用 'extern' 时,不能初始化变量,因为它只是将变量名指向先前定义的存储位置。

当您有多个文件并定义了一个全局变量或函数,而这些变量或函数也将在其他文件中使用时,则需要在另一个文件中使用extern来引用已定义的变量或函数。简单来说,extern用于在另一个文件中声明全局变量或函数。

正如以下解释,当两个或多个文件共享相同的全局变量或函数时,extern 修饰符最常用。

第一个文件:main.cpp

#include <iostream>
int count ;
extern void write_extern();
 
main() {
   count = 5;
   write_extern();
}

第二个文件:support.cpp

#include <iostream>

extern int count;

void write_extern(void) {
   std::cout << "Count is " << count << std::endl;
}

这里,extern关键字用于在另一个文件中声明count。现在编译这两个文件,方法如下:

$g++ main.cpp support.cpp -o write

这将生成write可执行程序,尝试执行write并检查结果如下:

$./write
5

可变存储类

mutable说明符仅适用于类对象(本教程稍后会讨论)。它允许对象的成员覆盖const成员函数。也就是说,const成员函数可以修改mutable成员。

C++中的运算符

运算符是一个符号,它告诉编译器执行特定的数学或逻辑运算。C++ 拥有丰富的内置运算符,并提供以下类型的运算符:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其他运算符

本章将逐一考察算术、关系、逻辑、位、赋值和其他运算符。

算术运算符

C++语言支持以下算术运算符:

假设变量A的值为10,变量B的值为20,则:

示例

运算符 描述 示例
+ 将两个操作数相加 A + B 将得到 30
- 从第一个操作数中减去第二个操作数 A - B 将得到 -10
* 将两个操作数相乘 A * B 将得到 200
/ 将分子除以分母 B / A 将得到 2
% 模运算符,返回整数除法后的余数 B % A 将得到 0
++ 自增运算符,将整数值增加一 A++ 将得到 11
-- 自减运算符,将整数值减少一 A-- 将得到 9

关系运算符

C++语言支持以下关系运算符:

假设变量A的值为10,变量B的值为20,则:

示例

运算符 描述 示例
== 检查两个操作数的值是否相等,如果相等则条件为真。 (A == B) 为假。
!= 检查两个操作数的值是否不相等,如果不相等则条件为真。 (A != B) 为真。
> 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 (A > B) 为假。
< 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 (A < B) 为真。
>= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 (A >= B) 为假。
<= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 (A <= B) 为真。

逻辑运算符

C++语言支持以下逻辑运算符。

假设变量A的值为1,变量B的值为0,则:

示例

运算符 描述 示例
&& 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 (A && B) 为假。
|| 称为逻辑或运算符。如果两个操作数中任何一个非零,则条件为真。 (A || B) 为真。
! 称为逻辑非运算符。用于反转其操作数的逻辑状态。如果一个条件为真,则逻辑非运算符将使其为假。 !(A && B) 为真。

位运算符

位运算符作用于位并执行逐位运算。&,| 和 ^ 的真值表如下:

p q p & q p | q p ^ q
0 0 0 0 0
0 1 0 1 1
1 1 1 1 0
1 0 0 1 1

假设 A = 60;B = 13;现在它们的二进制格式如下:

A = 0011 1100

B = 0000 1101

-----------------

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A  = 1100 0011

C++语言支持的位运算符列在下面的表格中。假设变量A的值为60,变量B的值为13,则:

示例

运算符 描述 示例
& 二进制与运算符:如果位同时存在于两个操作数中,则将其复制到结果中。 (A & B) 将得到 12,即 0000 1100
| 二进制或运算符:如果位存在于任一操作数中,则将其复制。 (A | B) 将得到 61,即 0011 1101
^ 二进制异或运算符:如果位在一个操作数中设置,但在另一个操作数中未设置,则将其复制。 (A ^ B) 将得到 49,即 0011 0001
~ 二进制反码运算符:是一元运算符,其作用是“翻转”位。 (~A ) 将得到 -61,由于是带符号二进制数,因此以二进制补码形式表示为 1100 0011。
<< 二进制左移运算符:左操作数的值向左移动由右操作数指定的位数。 A << 2 将得到 240,即 1111 0000
>> 二进制右移运算符:左操作数的值向右移动由右操作数指定的位数。 A >> 2 将得到 15,即 0000 1111

赋值运算符

C++语言支持以下赋值运算符:

示例

运算符 描述 示例
= 简单赋值运算符:将右操作数的值赋给左操作数。 C = A + B 将 A + B 的值赋给 C
+= 加法赋值运算符:将右操作数加到左操作数上,并将结果赋给左操作数。 C += A 等效于 C = C + A
-= 减法赋值运算符:从左操作数中减去右操作数,并将结果赋给左操作数。 C -= A 等效于 C = C - A
*= 乘法赋值运算符:将右操作数乘以左操作数,并将结果赋给左操作数。 C *= A 等效于 C = C * A
/= 除法赋值运算符:将左操作数除以右操作数,并将结果赋给左操作数。 C /= A 等效于 C = C / A
%= 模赋值运算符:使用两个操作数取模,并将结果赋给左操作数。 C %= A 等效于 C = C % A
<<= 左移赋值运算符。 C <<= 2 与 C = C << 2 相同
>>= 右移赋值运算符。 C >>= 2 与 C = C >> 2 相同
&= 位与赋值运算符。 C &= 2 与 C = C & 2 相同
^= 位异或赋值运算符。 C ^= 2 与 C = C ^ 2 相同
|= 位或赋值运算符。 C |= 2 与 C = C | 2 相同

其他运算符

下表列出了 C++ 支持的其他一些运算符。

序号 运算符 & 描述
1

sizeof

sizeof 运算符 返回变量的大小。例如,sizeof(a),其中 'a' 是整数,将返回 4。

2

Condition ? X : Y

条件运算符 (?)。如果 Condition 为真,则返回 X 的值,否则返回 Y 的值。

3

,

逗号运算符 导致执行一系列操作。整个逗号表达式的值是逗号分隔列表中最后一个表达式的值。

4

.(点) 和 ->(箭头)

成员运算符 用于引用类、结构体和联合体的各个成员。

5

类型转换

类型转换运算符 将一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。

6

&

地址运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。

7

*

指针运算符 * 指向一个变量。例如 *var; 将指向变量 var。

C++ 中的运算符优先级

运算符优先级决定了表达式中项的分组方式。这会影响表达式的计算方式。某些运算符的优先级高于其他运算符;例如,乘法运算符的优先级高于加法运算符:

例如 x = 7 + 3 * 2; 这里,x 被赋值为 13,而不是 20,因为运算符 * 的优先级高于 +,所以它首先与 3*2 相乘,然后加到 7 中。

这里,优先级最高的运算符出现在表的上方,优先级最低的运算符出现在表的下方。在一个表达式中,优先级较高的运算符将首先计算。

示例

类别 运算符 结合性
后缀 () [] -> . ++ -- 从左到右
一元 + - ! ~ ++ -- (type)* & sizeof 从右到左
乘法 * / % 从左到右
加法 + - 从左到右
移位 << >> 从左到右
关系 < <= > >= 从左到右
相等 == != 从左到右
位与 & 从左到右
位异或 ^ 从左到右
位或 | 从左到右
逻辑与 && 从左到右
逻辑或 || 从左到右
条件 ?: 从右到左
赋值 = += -= *= /= %= >>= <<= &= ^= |= 从右到左
逗号 , 从左到右

C++ 循环类型

可能会有这样的情况,您需要多次执行一段代码。通常情况下,语句是顺序执行的:函数中的第一个语句首先执行,然后是第二个语句,依此类推。

编程语言提供各种控制结构,允许更复杂的执行路径。

循环语句允许我们多次执行一条语句或一组语句,以下是大多数编程语言中循环语句的一般形式:

Loop Architecture

C++编程语言提供以下类型的循环来处理循环需求。

序号 循环类型 & 描述
1 while循环

当给定条件为真时,重复执行一条语句或一组语句。它在执行循环体之前测试条件。

2 for循环

多次执行一系列语句,并缩写管理循环变量的代码。

3 do...while循环

类似于‘while’语句,但它在循环体结束时测试条件。

4 嵌套循环

您可以在任何其他‘while’、‘for’或‘do..while’循环内使用一个或多个循环。

循环控制语句

循环控制语句改变其正常的执行顺序。当执行离开一个作用域时,在该作用域中创建的所有自动对象都被销毁。

C++支持以下控制语句。

序号 控制语句 & 描述
1 break语句

终止循环switch语句,并将执行转移到紧跟在循环或switch之后的语句。

2 continue语句

导致循环跳过其主体的其余部分,并在重新迭代之前立即重新测试其条件。

3 goto语句

将控制转移到带标签的语句。虽然不建议在程序中使用goto语句。

无限循环

如果条件永远不变成假,循环就会变成无限循环。传统的for循环常用于此目的。“for”循环的三个表达式都不必非要存在,您可以通过将条件表达式留空来创建一个无限循环。

#include <iostream>
using namespace std;
 
int main () {
   for( ; ; ) {
      printf("This loop will run forever.\n");
   }

   return 0;
}

当条件表达式不存在时,它被认为是真。您可以有初始化和增量表达式,但是C++程序员更常用'for (;;)'结构来表示无限循环。

注意 −您可以通过按下Ctrl + C键来终止无限循环。

C++决策语句

决策结构要求程序员指定一个或多个条件,由程序进行评估或测试,以及如果条件被确定为真则要执行的语句,以及可选地,如果条件被确定为假则要执行的其他语句。

以下是大多数编程语言中常见的一种典型决策结构的通用形式:

C++ decision making

C++编程语言提供以下类型的决策语句。

序号 语句与描述
1 if语句

一个“if”语句由一个布尔表达式后跟一个或多个语句组成。

2 if...else语句

一个“if”语句后面可以跟一个可选的“else”语句,当布尔表达式为假时执行。

3 switch语句

一个“switch”语句允许测试一个变量是否与值的列表相等。

4 嵌套if语句

您可以在另一个if或else if语句中使用一个if或else if语句。

5 嵌套switch语句

您可以在另一个switch语句中使用一个switch语句。

?: 运算符

我们在上一章中介绍了条件运算符“?:”,它可以用来替换if...else语句。它具有以下通用形式:

Exp1 ? Exp2 : Exp3;

Exp1、Exp2和Exp3是表达式。注意冒号的使用和位置。

“?”表达式的值是这样的:Exp1被计算。如果为真,则计算Exp2,并成为整个“?”表达式的值。如果Exp1为假,则计算Exp3,其值成为表达式的值。

C++ 函数

函数是一组一起执行任务的语句。每个C++程序至少有一个函数,即main(),并且所有最简单的程序都可以定义附加函数。

您可以将代码分成单独的函数。您如何将代码划分到不同的函数取决于您,但逻辑上划分通常是这样的:每个函数执行一项特定任务。

函数声明告诉编译器有关函数的名称、返回类型和参数的信息。函数定义提供了函数的实际主体。

C++标准库提供了许多内置函数,您的程序可以调用这些函数。例如,函数strcat()用于连接两个字符串,函数memcpy()用于将一个内存位置复制到另一个位置,还有许多其他函数。

函数被称为方法、子例程或过程等各种名称。

定义函数

C++函数定义的通用形式如下:

return_type function_name( parameter list ) {
   body of the function
}

C++函数定义由函数头和函数体组成。以下是函数的所有部分:

  • 返回类型 −函数可以返回值。return_type是函数返回的值的数据类型。有些函数执行所需的操作而不返回值。在这种情况下,return_type是关键字void

  • 函数名 −这是函数的实际名称。函数名和参数列表一起构成函数签名。

  • 参数 −参数就像一个占位符。当调用函数时,您将值传递给参数。这个值被称为实际参数或实参。参数列表是指函数的参数的类型、顺序和数量。参数是可选的;也就是说,函数可能不包含任何参数。

  • 函数体 −函数体包含定义函数功能的一组语句。

示例

以下是名为max()函数的源代码。此函数接受两个参数num1和num2,并返回两者中较大的一个:

// function returning the max between two numbers
 
int max(int num1, int num2) {
   // local variable declaration
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; 
}

函数声明

函数声明告诉编译器有关函数名称以及如何调用函数的信息。函数的实际主体可以单独定义。

函数声明具有以下部分:

return_type function_name( parameter list );

对于上面定义的函数max(),以下是函数声明:

int max(int num1, int num2);

参数名称在函数声明中并不重要,只需要它们的类型,因此以下也是有效的声明:

int max(int, int);

当您在一个源文件中定义一个函数并在另一个文件中调用该函数时,需要进行函数声明。在这种情况下,您应该在调用函数的文件顶部声明该函数。

调用函数

在创建C++函数时,您会给出函数必须执行的操作的定义。要使用函数,您必须调用或调用该函数。

当程序调用函数时,程序控制权将转移到被调用的函数。被调用的函数执行定义的任务,当执行其返回语句或到达其函数结束的闭合大括号时,它将程序控制权返回到主程序。

要调用函数,您只需传递所需的函数名称以及参数,如果函数返回值,则可以存储返回值。例如:

#include <iostream>
using namespace std;
 
// function declaration
int max(int num1, int num2);
 
int main () {
   // local variable declaration:
   int a = 100;
   int b = 200;
   int ret;
 
   // calling a function to get max value.
   ret = max(a, b);
   cout << "Max value is : " << ret << endl;
 
   return 0;
}
 
// function returning the max between two numbers
int max(int num1, int num2) {
   // local variable declaration
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; 
}

我将max()函数与main()函数一起保留,并编译了源代码。在运行最终的可执行文件时,它将产生以下结果:

Max value is : 200

函数参数

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数

形式参数在函数内部的行为类似于其他局部变量,并在进入函数时创建,并在退出函数时销毁。

在调用函数时,参数可以通过两种方式传递给函数:

序号 调用类型与描述
1 按值调用

此方法将参数的实际值复制到函数的形式参数中。在这种情况下,对函数内部参数所做的更改不会影响参数。

2 按指针调用

此方法将参数的地址复制到形式参数中。在函数内部,该地址用于访问调用中使用的实际参数。这意味着对参数所做的更改会影响参数。

3 按引用调用

此方法将参数的引用复制到形式参数中。在函数内部,该引用用于访问调用中使用的实际参数。这意味着对参数所做的更改会影响参数。

默认情况下,C++使用按值调用传递参数。一般来说,这意味着函数中的代码不能更改用于调用函数的参数,而上述示例在调用max()函数时使用了相同的方法。

参数的默认值

定义函数时,可以为每个最后一个参数指定默认值。如果在调用函数时对应的参数留空,则将使用此值。

这是通过使用赋值运算符并在函数定义中为参数赋值来完成的。如果在调用函数时未为该参数传递值,则使用给定的默认值,但如果指定了值,则忽略此默认值,并使用传递的值。考虑以下示例:

#include <iostream>
using namespace std;
 
int sum(int a, int b = 20) {
   int result;
   result = a + b;
  
   return (result);
}
int main () {
   // local variable declaration:
   int a = 100;
   int b = 200;
   int result;
 
   // calling a function to add the values.
   result = sum(a, b);
   cout << "Total value is :" << result << endl;

   // calling a function again as follows.
   result = sum(a);
   cout << "Total value is :" << result << endl;
 
   return 0;
}

编译并执行上述代码时,会产生以下结果:

Total value is :300
Total value is :120

C++中的数字

通常,当我们使用数字时,我们使用诸如int、short、long、float和double等原始数据类型。在讨论C++数据类型时,已经解释了数字数据类型、其可能值和数字范围。

在C++中定义数字

您已经在前面章节中给出的各种示例中定义了数字。这是一个在C++中定义各种类型数字的另一个综合示例:

#include <iostream>
using namespace std;
 
int main () {
   // number definition:
   short  s;
   int    i;
   long   l;
   float  f;
   double d;
   
   // number assignments;
   s = 10;      
   i = 1000;    
   l = 1000000; 
   f = 230.47;  
   d = 30949.374;
   
   // number printing;
   cout << "short  s :" << s << endl;
   cout << "int    i :" << i << endl;
   cout << "long   l :" << l << endl;
   cout << "float  f :" << f << endl;
   cout << "double d :" << d << endl;
 
   return 0;
}

编译并执行上述代码时,会产生以下结果:

short  s :10
int    i :1000
long   l :1000000
float  f :230.47
double d :30949.4

C++中的数学运算

除了您可以创建的各种函数外,C++还包含一些您可以使用的有用函数。这些函数可在标准C和C++库中使用,称为内置函数。这些是可以包含在您的程序中然后使用的函数。

C++有一套丰富的数学运算,可以对各种数字进行运算。下表列出了C++中可用的一些有用的内置数学函数。

要使用这些函数,您需要包含math头文件<cmath>

序号 函数与用途
1

double cos(double);

此函数接受一个角度(作为double)并返回余弦值。

2

double sin(double);

此函数接受一个角度(作为double)并返回正弦值。

3

double tan(double);

此函数接受一个角度(作为double)并返回正切值。

4

double log(double);

此函数接受一个数字并返回该数字的自然对数。

5

double pow(double, double);

第一个是您要提升的数字,第二个是您要提升它的幂。

6

double hypot(double, double);

如果您将直角三角形的两条边的长度传递给此函数,它将返回斜边的长度。

7

double sqrt(double);

您将一个数字传递给此函数,它会给出平方根。

8

int abs(int);

此函数返回传递给它的整数的绝对值。

9

double fabs(double);

此函数返回传递给它的任何十进制数的绝对值。

10

double floor(double);

查找小于或等于传递给它的参数的整数。

以下是一个简单的示例,用于显示一些数学运算:

#include <iostream>
#include <cmath>
using namespace std;
 
int main () {
   // number definition:
   short  s = 10;
   int    i = -1000;
   long   l = 100000;
   float  f = 230.47;
   double d = 200.374;

   // mathematical operations;
   cout << "sin(d) :" << sin(d) << endl;
   cout << "abs(i)  :" << abs(i) << endl;
   cout << "floor(d) :" << floor(d) << endl;
   cout << "sqrt(f) :" << sqrt(f) << endl;
   cout << "pow( d, 2) :" << pow(d, 2) << endl;
 
   return 0;
}

编译并执行上述代码时,会产生以下结果:

sign(d)     :-0.634939
abs(i)      :1000
floor(d)    :200
sqrt(f)     :15.1812
pow( d, 2 ) :40149.7

C++中的随机数

在许多情况下,您希望生成一个随机数。实际上,您需要了解两个关于随机数生成的函数。第一个是rand(),此函数仅返回伪随机数。解决此问题的方法是首先调用srand()函数。

以下是一个生成一些随机数的简单示例。此示例使用time()函数获取系统时间的秒数,以随机播种rand()函数:

#include <iostream>
#include <ctime>
#include <cstdlib>

using namespace std;
 
int main () {
   int i,j;
 
   // set the seed
   srand( (unsigned)time( NULL ) );

   /* generate 10  random numbers. */
   for( i = 0; i < 10; i++ ) {
      // generate actual random number
      j = rand();
      cout <<" Random Number : " << j << endl;
   }

   return 0;
}

编译并执行上述代码时,会产生以下结果:

Random Number : 1748144778
Random Number : 630873888
Random Number : 2134540646
Random Number : 219404170
Random Number : 902129458
Random Number : 920445370
Random Number : 1319072661
Random Number : 257938873
Random Number : 1256201101
Random Number : 580322989

C++ 数组

C++提供了一种数据结构数组,它存储相同类型的元素的固定大小的顺序集合。数组用于存储数据集合,但通常将数组视为相同类型变量的集合更有用。

无需声明单个变量,例如number0、number1、...和number99,您可以声明一个数组变量,例如numbers,并使用numbers[0]、numbers[1]和...numbers[99]来表示单个变量。数组中的特定元素通过索引访问。

所有数组都由连续的内存位置组成。最低地址对应于第一个元素,最高地址对应于最后一个元素。

声明数组

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

type arrayName [ arraySize ];

这被称为一维数组。arraySize 必须是一个大于零的整数常量,而 type 可以是任何有效的 C++ 数据类型。例如,要声明一个名为 balance 的 10 个元素的双精度浮点数类型数组,可以使用以下语句:

double balance[10];

数组初始化

您可以逐个初始化 C++ 数组元素,也可以使用以下单一语句进行初始化:

double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0};

大括号 { } 中的值的数量不能大于我们在方括号 [ ] 中为数组声明的元素数量。以下是一个为数组的单个元素赋值的示例:

如果您省略数组的大小,则会创建一个足够大的数组来容纳初始化的值。因此,如果您编写:

double balance[] = {1000.0, 2.0, 3.4, 17.0, 50.0};

您将创建与前一个示例中完全相同的数组。

balance[4] = 50.0;

上述语句将数组中第 5 个元素的值赋值为 50.0。由于所有数组的第一个元素的索引都是 0(也称为基索引),因此索引为 4 的元素将是第 5 个元素,即最后一个元素。以下是我们上面讨论的相同数组的图示:

Array Presentation

访问数组元素

通过索引数组名称来访问元素。这是通过在数组名称之后方括号中放置元素的索引来完成的。例如:

double salary = balance[9];

上述语句将从数组中取出第 10 个元素并将该值赋给 salary 变量。以下是一个示例,它将使用上述三个概念,即声明、赋值和访问数组:

#include <iostream>
using namespace std;
 
#include <iomanip>
using std::setw;
 
int main () {

   int n[ 10 ]; // n is an array of 10 integers
 
   // initialize elements of array n to 0          
   for ( int i = 0; i < 10; i++ ) {
      n[ i ] = i + 100; // set element at location i to i + 100
   }
   cout << "Element" << setw( 13 ) << "Value" << endl;
 
   // output each array element's value                      
   for ( int j = 0; j < 10; j++ ) {
      cout << setw( 7 )<< j << setw( 13 ) << n[ j ] << endl;
   }
 
   return 0;
}

此程序使用 setw() 函数来格式化输出。当以上代码编译并执行时,它会产生以下结果:

Element        Value
      0          100
      1          101
      2          102
      3          103
      4          104
      5          105
      6          106
      7          107
      8          108
      9          109

C++ 中的数组

数组对于 C++ 非常重要,需要更详细的解释。以下是一些 C++ 程序员应该了解的重要概念:

序号 概念和描述
1 多维数组

C++ 支持多维数组。多维数组最简单的形式是二维数组。

2 指向数组的指针

您可以通过简单地指定数组名(不带任何索引)来生成指向数组第一个元素的指针。

3 将数组传递给函数

您可以通过指定数组名(不带索引)将指向数组的指针传递给函数。

4 从函数返回数组

C++ 允许函数返回一个数组。

C++ 字符串

C++ 提供以下两种类型的字符串表示:

  • C 风格的字符字符串。
  • 标准 C++ 引入的 string 类类型。

C 风格的字符字符串

C 风格的字符字符串起源于 C 语言,并在 C++ 中继续得到支持。这个字符串实际上是一个以字符 '\0' 结尾的一维字符数组。因此,一个空终止字符串包含构成字符串的字符,后跟一个字符。

以下声明和初始化创建一个包含单词“Hello”的字符串。为了在数组末尾保留空字符,包含字符串的字符数组的大小比单词“Hello”中的字符数多一个。

char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

如果您遵循数组初始化规则,则可以将上述语句编写如下:

char greeting[] = "Hello";

以下是上述在 C/C++ 中定义的字符串的内存表示:

String Presentation in C/C++

实际上,您不必在字符串常量的末尾放置空字符。C++ 编译器在初始化数组时会自动在字符串末尾放置 '\0'。让我们尝试打印上述字符串:

#include <iostream>

using namespace std;

int main () {

   char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

   cout << "Greeting message: ";
   cout << greeting << endl;

   return 0;
}

编译并执行上述代码时,会产生以下结果:

Greeting message: Hello

C++ 支持广泛的功能,这些功能可以操作以 null 结尾的字符串:

序号 函数与用途
1

strcpy(s1, s2);

将字符串 s2 复制到字符串 s1 中。

2

strcat(s1, s2);

将字符串 s2 连接到字符串 s1 的末尾。

3

strlen(s1);

返回字符串 s1 的长度。

4

strcmp(s1, s2);

如果 s1 和 s2 相同,则返回 0;如果 s1s2,则返回大于 0 的值。

5

strchr(s1, ch);

返回指向字符串 s1 中字符 ch 的第一次出现的指针。

6

strstr(s1, s2);

返回指向字符串 s1 中字符串 s2 的第一次出现的指针。

以下示例使用了一些上述函数:

#include <iostream>
#include <cstring>

using namespace std;

int main () {

   char str1[10] = "Hello";
   char str2[10] = "World";
   char str3[10];
   int  len ;

   // copy str1 into str3
   strcpy( str3, str1);
   cout << "strcpy( str3, str1) : " << str3 << endl;

   // concatenates str1 and str2
   strcat( str1, str2);
   cout << "strcat( str1, str2): " << str1 << endl;

   // total lenghth of str1 after concatenation
   len = strlen(str1);
   cout << "strlen(str1) : " << len << endl;

   return 0;
}

当以上代码编译并执行时,会产生类似以下的结果:

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

C++ 中的字符串类

标准 C++ 库提供了一个string 类类型,它支持上述所有操作,以及更多功能。让我们检查以下示例:

#include <iostream>
#include <string>

using namespace std;

int main () {

   string str1 = "Hello";
   string str2 = "World";
   string str3;
   int  len ;

   // copy str1 into str3
   str3 = str1;
   cout << "str3 : " << str3 << endl;

   // concatenates str1 and str2
   str3 = str1 + str2;
   cout << "str1 + str2 : " << str3 << endl;

   // total length of str3 after concatenation
   len = str3.size();
   cout << "str3.size() :  " << len << endl;

   return 0;
}

当以上代码编译并执行时,会产生类似以下的结果:

str3 : Hello
str1 + str2 : HelloWorld
str3.size() :  10

C++ 指针

C++ 指针易于学习且很有趣。一些 C++ 任务使用指针更容易完成,而其他 C++ 任务(例如动态内存分配)则无法在没有指针的情况下完成。

如您所知,每个变量都是一个内存位置,每个内存位置都有其定义的地址,可以使用表示内存地址的地址运算符 & 来访问。考虑以下内容,它将打印定义的变量的地址:

#include <iostream>

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

   cout << "Address of var1 variable: ";
   cout << &var1 << endl;

   cout << "Address of var2 variable: ";
   cout << &var2 << endl;

   return 0;
}

编译并执行上述代码时,会产生以下结果:

Address of var1 variable: 0xbfebd5c0
Address of var2 variable: 0xbfebd5b6

什么是指针?

指针是一个变量,其值是另一个变量的地址。与任何变量或常量一样,您必须在使用指针之前声明它。指针变量声明的一般形式是:

type *var-name;

这里,type 是指针的基类型;它必须是有效的 C++ 类型,而 var-name 是指针变量的名称。用于声明指针的星号与用于乘法的星号相同。但是,在此语句中,星号用于将变量指定为指针。以下是有效的指针声明:

int    *ip;    // pointer to an integer
double *dp;    // pointer to a double
float  *fp;    // pointer to a float
char   *ch     // pointer to character

所有指针的实际数据类型(无论是整数、浮点数、字符还是其他类型)都是相同的,都是表示内存地址的长十六进制数。不同数据类型指针之间的唯一区别在于指针指向的变量或常量的类型。

在 C++ 中使用指针

有一些重要的操作,我们将非常频繁地对指针进行这些操作。(a) 我们定义一个指针变量。(b) 将变量的地址赋给指针。(c) 最后访问指针变量中可用地址处的值。这是通过使用一元运算符 * 来完成的,它返回位于其操作数指定的地址处的变量的值。以下示例使用了这些操作:

#include <iostream>

using namespace std;

int main () {
   int  var = 20;   // actual variable declaration.
   int  *ip;        // pointer variable 

   ip = &var;       // store address of var in pointer variable

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

   // print the address stored in ip pointer variable
   cout << "Address stored in ip variable: ";
   cout << ip << endl;

   // access the value at the address available in pointer
   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++ 中的指针

指针有很多但很容易理解的概念,它们对于 C++ 编程非常重要。以下是一些 C++ 程序员应该了解的重要指针概念:

序号 概念和描述
1 空指针

C++ 支持空指针,它是一个值为零的常量,在几个标准库中定义。

2 指针运算

有四个算术运算符可以用于指针:++、--、+、-

3 指针与数组

指针和数组之间存在密切关系。

4 指针数组

您可以定义数组来保存多个指针。

5 指向指针的指针

C++ 允许您对指针进行指针操作,以此类推。

6 将指针传递给函数

通过引用或通过地址传递参数都可以使被调用函数在调用函数中更改传递的参数。

7 从函数返回指针

C++ 允许函数返回指向局部变量、静态变量和动态分配内存的指针。

C++ 引用

引用变量是别名,即已存在变量的另一个名称。一旦引用用变量初始化,就可以使用变量名或引用名来引用该变量。

引用与指针

引用经常与指针混淆,但引用和指针之间有三个主要区别:

  • 您不能有空引用。您必须始终能够假设引用已连接到合法的存储空间。

  • 一旦引用被初始化为一个对象,就不能将其更改为引用另一个对象。指针可以随时指向另一个对象。

  • 创建引用时必须对其进行初始化。指针可以随时初始化。

在 C++ 中创建引用

将变量名视为附加到变量在内存中的位置的标签。然后,您可以将引用视为附加到该内存位置的第二个标签。因此,您可以通过原始变量名或引用来访问变量的内容。例如,假设我们有以下示例:

int i = 17;

我们可以为 i 声明引用变量,如下所示。

int& r = i;

将这些声明中的 & 读作引用。因此,将第一个声明读作“r 是一个初始化为 i 的整数引用”,并将第二个声明读作“s 是一个初始化为 d 的双精度浮点数引用”。以下示例使用了 int 和 double 的引用:

#include <iostream>
 
using namespace std;
 
int main () {
   // declare simple variables
   int    i;
   double d;
 
   // declare reference variables
   int&    r = i;
   double& s = d;
   
   i = 5;
   cout << "Value of i : " << i << endl;
   cout << "Value of i reference : " << r  << endl;
 
   d = 11.7;
   cout << "Value of d : " << d << endl;
   cout << "Value of d reference : " << s  << endl;
   
   return 0;
}

当上述代码编译并执行时,它会产生以下结果:

Value of i : 5
Value of i reference : 5
Value of d : 11.7
Value of d reference : 11.7

引用通常用于函数参数列表和函数返回值。因此,以下与 C++ 引用相关的两个重要主题应该对 C++ 程序员清晰:

序号 概念和描述
1 作为参数的引用

C++ 支持比参数更安全地将引用作为函数参数传递。

2 作为返回值的引用

您可以像任何其他数据类型一样从 C++ 函数返回引用。

C++ 日期和时间

C++ 标准库没有提供合适的日期类型。C++ 从 C 继承了用于日期和时间操作的结构和函数。要访问与日期和时间相关的函数和结构,您需要在 C++ 程序中包含 头文件。

有四种与时间相关的类型:clock_t、time_t、size_ttm。clock_t、size_t 和 time_t 类型能够将系统时间和日期表示为某种整数。

结构类型 tm 以 C 结构的形式保存日期和时间,该结构具有以下元素:

struct tm {
   int tm_sec;   // seconds of minutes from 0 to 61
   int tm_min;   // minutes of hour from 0 to 59
   int tm_hour;  // hours of day from 0 to 24
   int tm_mday;  // day of month from 1 to 31
   int tm_mon;   // month of year from 0 to 11
   int tm_year;  // year since 1900
   int tm_wday;  // days since sunday
   int tm_yday;  // days since January 1st
   int tm_isdst; // hours of daylight savings time
}

以下是在 C 或 C++ 中使用日期和时间时使用的重要函数。所有这些函数都是标准 C 和 C++ 库的一部分,您可以使用下面给出的 C++ 标准库参考来检查它们的详细信息。

序号 函数与用途
1

time_t time(time_t *time);

这将返回系统当前日历时间,单位为自 1970 年 1 月 1 日以来经过的秒数。如果系统没有时间,则返回 .1。

2

char *ctime(const time_t *time);

这将返回一个指向以下形式字符串的指针:day month year hours:minutes:seconds year\n\0

3

struct tm *localtime(const time_t *time);

这将返回一个指向表示本地时间的 tm 结构的指针。

4

clock_t clock(void);

这将返回一个近似表示调用程序运行时间的数值。如果时间不可用,则返回 .1。

5

char * asctime ( const struct tm * time );

这将返回一个指向包含 time 指向的结构中存储的信息的字符串的指针,该信息已转换为以下形式:day month date hours:minutes:seconds year\n\0

6

struct tm *gmtime(const time_t *time);

此函数返回一个指向 tm 结构体类型的指针,该结构体表示时间。时间以协调世界时 (UTC) 表示,它本质上与格林威治标准时间 (GMT) 相同。

7

time_t mktime(struct tm *time);

此函数返回 time 指针指向的结构体中时间对应的日历时间。

8

double difftime ( time_t time2, time_t time1 );

此函数计算 time1 和 time2 之间的秒数差。

9

size_t strftime();

此函数可用于以特定格式格式化日期和时间。

当前日期和时间

假设您想检索当前系统日期和时间,无论是本地时间还是协调世界时 (UTC)。以下是实现此目的的示例:

#include <iostream>
#include <ctime>

using namespace std;

int main() {
   // current date/time based on current system
   time_t now = time(0);
   
   // convert now to string form
   char* dt = ctime(&now);

   cout << "The local date and time is: " << dt << endl;

   // convert now to tm struct for UTC
   tm *gmtm = gmtime(&now);
   dt = asctime(gmtm);
   cout << "The UTC date and time is:"<< dt << endl;
}

编译并执行上述代码时,会产生以下结果:

The local date and time is: Sat Jan  8 20:07:41 2011

The UTC date and time is:Sun Jan  9 03:07:41 2011

使用 struct tm 格式化时间

在 C 或 C++ 中处理日期和时间时,tm 结构体非常重要。此结构体以 C 结构体的形式保存日期和时间,如上所述。大多数与时间相关的函数都使用 tm 结构体。以下是一个使用各种日期和时间相关函数和 tm 结构体的示例:

在本节中使用结构体时,我假设您已经基本了解 C 结构体以及如何使用箭头 -> 运算符访问结构体成员。

#include <iostream>
#include <ctime>

using namespace std;

int main() {
   // current date/time based on current system
   time_t now = time(0);

   cout << "Number of sec since January 1,1970 is:: " << now << endl;

   tm *ltm = localtime(&now);

   // print various components of tm structure.
   cout << "Year:" << 1900 + ltm->tm_year<<endl;
   cout << "Month: "<< 1 + ltm->tm_mon<< endl;
   cout << "Day: "<< ltm->tm_mday << endl;
   cout << "Time: "<< 5+ltm->tm_hour << ":";
   cout << 30+ltm->tm_min << ":";
   cout << ltm->tm_sec << endl;
}

编译并执行上述代码时,会产生以下结果:

Number of sec since January 1,1970 is:: 1588485717
Year:2020
Month: 5
Day: 3
Time: 11:31:57

C++ 基本输入/输出

C++ 标准库提供了一套广泛的输入/输出功能,我们将在后续章节中介绍。本章将讨论 C++ 编程所需的最基本和最常见的 I/O 操作。

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

I/O 库头文件

以下头文件对于 C++ 程序很重要:

序号 头文件 & 函数及说明
1

<iostream>

此文件定义了cin、cout、cerrclog 对象,它们分别对应于标准输入流、标准输出流、无缓冲的标准错误流和带缓冲的标准错误流。

2

<iomanip>

此文件声明了用于使用所谓的参数化流操作符(例如setwsetprecision)执行格式化 I/O 的服务。

3

<fstream>

此文件声明了用于用户控制的文件处理的服务。我们将在文件和流相关的章节中详细讨论。

标准输出流 (cout)

预定义对象coutostream 类的实例。cout 对象被称为“连接到”标准输出设备,通常是显示屏。cout 与流插入运算符一起使用,该运算符写为 <<,即两个小于号,如下例所示。

#include <iostream>
 
using namespace std;
 
int main() {
   char str[] = "Hello C++";
 
   cout << "Value of str is : " << str << endl;
}

编译并执行上述代码时,会产生以下结果:

Value of str is : Hello C++

C++ 编译器还会确定要输出的变量的数据类型,并选择适当的流插入运算符来显示值。<< 运算符被重载以输出内置类型整数、浮点数、双精度浮点数、字符串和指针值的数据项。

插入运算符 << 可以在单个语句中使用多次,如上所示,endl 用于在行尾添加换行符。

标准输入流 (cin)

预定义对象cinistream 类的实例。cin 对象被称为连接到标准输入设备,通常是键盘。cin 与流提取运算符一起使用,该运算符写为 >>,即两个大于号,如下例所示。

#include <iostream>
 
using namespace std;
 
int main() {
   char name[50];
 
   cout << "Please enter your name: ";
   cin >> name;
   cout << "Your name is: " << name << endl;
 
}

当编译并执行上述代码时,它将提示您输入名称。您输入一个值,然后按 Enter 键查看以下结果:

Please enter your name: cplusplus
Your name is: cplusplus

C++ 编译器还会确定输入值的类型,并选择适当的流提取运算符来提取值并将其存储到给定的变量中。

流提取运算符 >> 可以在单个语句中使用多次。要请求多个数据,可以使用以下方法:

cin >> name >> age;

这将等效于以下两个语句:

cin >> name;
cin >> age;

标准错误流 (cerr)

预定义对象cerrostream 类的实例。cerr 对象被称为连接到标准错误设备,这也是显示屏,但是cerr 对象是无缓冲的,每次向 cerr 进行流插入都会立即显示其输出。

cerr 也与流插入运算符一起使用,如下例所示。

#include <iostream>
 
using namespace std;
 
int main() {
   char str[] = "Unable to read....";
 
   cerr << "Error message : " << str << endl;
}

编译并执行上述代码时,会产生以下结果:

Error message : Unable to read....

标准日志流 (clog)

预定义对象clogostream 类的实例。clog 对象被称为连接到标准错误设备,这也是显示屏,但是clog 对象是有缓冲的。这意味着每次向 clog 进行插入都可能导致其输出保存在缓冲区中,直到缓冲区填满或缓冲区被刷新。

clog 也与流插入运算符一起使用,如下例所示。

#include <iostream>
 
using namespace std;
 
int main() {
   char str[] = "Unable to read....";
 
   clog << "Error message : " << str << endl;
}

编译并执行上述代码时,会产生以下结果:

Error message : Unable to read....

通过这些小的例子,您可能无法看到 cout、cerr 和 clog 之间的任何区别,但在编写和执行大型程序时,差异就会变得明显。因此,最好使用 cerr 流显示错误消息,而显示其他日志消息时则应使用 clog。

C++ 数据结构

C/C++ 数组允许您定义组合几种相同类型的数据项的变量,但结构体是另一种用户定义的数据类型,它允许您组合不同类型的数据项。

结构体用于表示记录,假设您想跟踪图书馆中书籍的信息。您可能希望跟踪每本书的以下属性:

  • 书名
  • 作者
  • 主题
  • 图书 ID

定义结构体

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

struct [structure tag] {
   member definition;
   member definition;
   ...
   member definition;
} [one or more structure variables];  

结构体标签是可选的,每个成员定义都是正常的变量定义,例如 int i; 或 float f; 或任何其他有效的变量定义。在结构体定义的末尾,在最后一个分号之前,您可以指定一个或多个结构体变量,但这是可选的。以下是声明 Book 结构体的方式:

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

访问结构体成员

要访问结构体的任何成员,我们使用成员访问运算符 (.)。成员访问运算符被编码为结构体变量名和我们希望访问的结构体成员之间的句点。您将使用struct 关键字定义结构体类型的变量。以下示例解释了结构体的用法:

#include <iostream>
#include <cstring>
 
using namespace std;
 
struct Books {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};
 
int main() {
   struct Books Book1;        // Declare Book1 of type Book
   struct Books Book2;        // Declare Book2 of type Book
 
   // book 1 specification
   strcpy( Book1.title, "Learn C++ Programming");
   strcpy( Book1.author, "Chand Miyan"); 
   strcpy( Book1.subject, "C++ Programming");
   Book1.book_id = 6495407;

   // book 2 specification
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Yakit Singha");
   strcpy( Book2.subject, "Telecom");
   Book2.book_id = 6495700;
 
   // Print Book1 info
   cout << "Book 1 title : " << Book1.title <<endl;
   cout << "Book 1 author : " << Book1.author <<endl;
   cout << "Book 1 subject : " << Book1.subject <<endl;
   cout << "Book 1 id : " << Book1.book_id <<endl;

   // Print Book2 info
   cout << "Book 2 title : " << Book2.title <<endl;
   cout << "Book 2 author : " << Book2.author <<endl;
   cout << "Book 2 subject : " << Book2.subject <<endl;
   cout << "Book 2 id : " << Book2.book_id <<endl;

   return 0;
}

编译并执行上述代码时,会产生以下结果:

Book 1 title : Learn C++ Programming
Book 1 author : Chand Miyan
Book 1 subject : C++ Programming
Book 1 id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Yakit Singha
Book 2 subject : Telecom
Book 2 id : 6495700

结构体作为函数参数

您可以像传递任何其他变量或指针一样传递结构体作为函数参数。您将像在上述示例中一样访问结构体变量:

#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() {
   struct Books Book1;        // Declare Book1 of type Book
   struct Books Book2;        // Declare Book2 of type Book
 
   // book 1 specification
   strcpy( Book1.title, "Learn C++ Programming");
   strcpy( Book1.author, "Chand Miyan"); 
   strcpy( Book1.subject, "C++ Programming");
   Book1.book_id = 6495407;

   // book 2 specification
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Yakit Singha");
   strcpy( Book2.subject, "Telecom");
   Book2.book_id = 6495700;
 
   // Print Book1 info
   printBook( Book1 );

   // Print Book2 info
   printBook( Book2 );

   return 0;
}
void printBook( struct Books book ) {
   cout << "Book title : " << book.title <<endl;
   cout << "Book author : " << book.author <<endl;
   cout << "Book subject : " << book.subject <<endl;
   cout << "Book id : " << book.book_id <<endl;
}

编译并执行上述代码时,会产生以下结果:

Book title : Learn C++ Programming
Book author : Chand Miyan
Book subject : C++ Programming
Book id : 6495407
Book title : Telecom Billing
Book author : Yakit Singha
Book subject : Telecom
Book id : 6495700

指向结构体的指针

您可以像定义指向任何其他变量的指针一样定义指向结构体的指针,如下所示:

struct Books *struct_pointer;

现在,您可以将结构体变量的地址存储在上述定义的指针变量中。要查找结构体变量的地址,请在结构体名称前放置 & 运算符,如下所示:

struct_pointer = &Book1;

要使用指向该结构体的指针访问结构体的成员,必须使用 -> 运算符,如下所示:

struct_pointer->title;

让我们使用结构体指针重写上述示例,希望这将更容易理解这个概念:

#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() {
   struct Books Book1;        // Declare Book1 of type Book
   struct Books Book2;        // Declare Book2 of type Book
 
   // Book 1 specification
   strcpy( Book1.title, "Learn C++ Programming");
   strcpy( Book1.author, "Chand Miyan"); 
   strcpy( Book1.subject, "C++ Programming");
   Book1.book_id = 6495407;

   // Book 2 specification
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Yakit Singha");
   strcpy( Book2.subject, "Telecom");
   Book2.book_id = 6495700;
 
   // Print Book1 info, passing address of structure
   printBook( &Book1 );

   // Print Book1 info, passing address of structure
   printBook( &Book2 );

   return 0;
}

// This function accept pointer to structure as parameter.
void printBook( struct Books *book ) {
   cout << "Book title : " << book->title <<endl;
   cout << "Book author : " << book->author <<endl;
   cout << "Book subject : " << book->subject <<endl;
   cout << "Book id : " << book->book_id <<endl;
}

编译并执行上述代码时,会产生以下结果:

Book title : Learn C++ Programming
Book author : Chand Miyan
Book subject : C++ Programming
Book id : 6495407
Book title : Telecom Billing
Book author : Yakit Singha
Book subject : Telecom
Book id : 6495700

typedef 关键字

有一种更简单的方法来定义结构体,或者您可以为创建的类型创建“别名”。例如:

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

现在,您可以直接使用Books 来定义Books 类型的变量,而无需使用 struct 关键字。以下是一个示例:

Books Book1, Book2;

您也可以对非结构体使用typedef 关键字,如下所示:

typedef long int *pint32;
 
pint32 x, y, z;

x、y 和 z 都是指向长整型的指针。

C++ 类和对象

C++ 编程的主要目的是为 C 编程语言添加面向对象特性,类是支持面向对象编程的 C++ 的核心特性,通常称为用户定义类型。

类用于指定对象的结构,它将数据表示和操作该数据的方法组合到一个整洁的包中。类中的数据和函数称为类的成员。

C++ 类定义

定义类时,定义了数据类型的蓝图。这实际上并没有定义任何数据,但它确实定义了类名的含义,也就是说,类的对象将包含什么以及可以对这样的对象执行什么操作。

类定义以关键字class 后跟类名开头;以及用一对花括号括起来的类体。类定义后面必须跟一个分号或一个声明列表。例如,我们使用关键字class 定义了 Box 数据类型,如下所示:

class Box {
   public:
      double length;   // Length of a box
      double breadth;  // Breadth of a box
      double height;   // Height of a box
};

关键字public 确定其后类成员的访问属性。公共成员可以在类的作用域内的任何地方从类外部访问。您还可以将类的成员指定为privateprotected,我们将在小节中讨论。

定义 C++ 对象

类为对象提供了蓝图,因此基本上对象是由类创建的。我们用与声明基本类型的变量完全相同的声明来声明类的对象。以下语句声明了两个 Box 类的对象:

Box Box1;          // Declare Box1 of type Box
Box Box2;          // Declare Box2 of type Box

Box1 和 Box2 两个对象都将拥有自己的数据成员副本。

访问数据成员

可以使用直接成员访问运算符 (.) 访问类对象的公共数据成员。让我们尝试以下示例来使事情更清晰:

#include <iostream>

using namespace std;

class Box {
   public:
      double length;   // Length of a box
      double breadth;  // Breadth of a box
      double height;   // Height of a box
};

int main() {
   Box Box1;        // Declare Box1 of type Box
   Box Box2;        // Declare Box2 of type Box
   double volume = 0.0;     // Store the volume of a box here
 
   // box 1 specification
   Box1.height = 5.0; 
   Box1.length = 6.0; 
   Box1.breadth = 7.0;

   // box 2 specification
   Box2.height = 10.0;
   Box2.length = 12.0;
   Box2.breadth = 13.0;
   
   // volume of box 1
   volume = Box1.height * Box1.length * Box1.breadth;
   cout << "Volume of Box1 : " << volume <<endl;

   // volume of box 2
   volume = Box2.height * Box2.length * Box2.breadth;
   cout << "Volume of Box2 : " << volume <<endl;
   return 0;
}

编译并执行上述代码时,会产生以下结果:

Volume of Box1 : 210
Volume of Box2 : 1560

需要注意的是,不能使用直接成员访问运算符 (.) 直接访问私有和受保护成员。我们将学习如何访问私有和受保护成员。

详细介绍类和对象

到目前为止,您已经对 C++ 类和对象有了非常基本的了解。还有更多关于 C++ 类和对象的有趣概念,我们将在下面列出的各个小节中讨论:

序号 概念和描述
1 类成员函数

类的成员函数是一个函数,其定义或原型与任何其他变量一样,都位于类定义内。

2 类访问修饰符

类成员可以定义为公有的(public)、私有的(private)或受保护的(protected)。默认情况下,成员被认为是私有的。

3 构造函数和析构函数

类的构造函数是类中的一种特殊函数,在创建类的新的对象时被调用。析构函数也是一种特殊函数,在创建的对象被删除时被调用。

4 复制构造函数

复制构造函数是一种构造函数,它通过使用之前创建的同一类的对象来初始化对象从而创建一个对象。

5 友元函数

友元函数被允许完全访问类的私有和受保护成员。

6 内联函数

对于内联函数,编译器尝试将函数体中的代码扩展到函数调用的位置。

7 this 指针

每个对象都有一个特殊的指针this,它指向对象本身。

8 指向C++类的指针

指向类的指针与指向结构的指针完全相同。实际上,类只是一个包含函数的结构。

9 类的静态成员

类的成员数据和成员函数都可以声明为静态的。

C++ 继承

面向对象编程中最重要概念之一是继承。继承允许我们根据另一个类来定义类,这使得创建和维护应用程序更加容易。这也提供了重用代码功能和加快实现时间的机会。

创建类时,程序员可以指定新类应该继承现有类的成员,而不是编写全新的成员数据和成员函数。这个现有的类称为基类,新类称为派生类

继承的概念实现了is a关系。例如,哺乳动物是动物,狗是哺乳动物,因此狗也是动物,等等。

基类和派生类

一个类可以从多个类派生,这意味着它可以继承多个基类的成员数据和函数。为了定义派生类,我们使用类派生列表来指定基类。

class derived-class: access-specifier base-class

其中,访问说明符是public、protectedprivate之一,基类是先前定义的类的名称。如果未使用访问说明符,则默认为private。

考虑一个基类Shape及其派生类Rectangle

#include <iostream>
 
using namespace std;

// Base class
class Shape {
   public:
      void setWidth(int w) {
         width = w;
      }
      void setHeight(int h) {
         height = h;
      }
      
   protected:
      int width;
      int height;
};

// Derived class
class Rectangle: public Shape {
   public:
      int getArea() { 
         return (width * height); 
      }
};

int main(void) {
   Rectangle Rect;
 
   Rect.setWidth(5);
   Rect.setHeight(7);

   // Print the area of the object.
   cout << "Total area: " << Rect.getArea() << endl;

   return 0;
}

编译并执行上述代码时,会产生以下结果:

Total area: 35

访问控制和继承

派生类可以访问其基类的所有非私有成员。因此,基类中不应该被派生类的成员函数访问的成员应该在基类中声明为私有的。

我们可以按照以下方式总结不同访问类型:

访问权限 public protected private
同一类
派生类
外部类

派生类继承所有基类方法,但以下情况除外:

  • 基类的构造函数、析构函数和复制构造函数。
  • 基类的重载运算符。
  • 基类的友元函数。

继承类型

从基类派生类时,基类可以通过public、protectedprivate继承方式继承。继承类型由访问说明符指定,如上所述。

我们几乎不使用protectedprivate继承,但public继承是常用的。使用不同类型的继承时,应用以下规则:

  • 公有继承 - 从公有基类派生类时,基类的公有成员成为派生类的公有成员,基类的受保护成员成为派生类的受保护成员。基类的私有成员永远无法直接从派生类访问,但可以通过调用基类的公有受保护成员来访问。

  • 受保护继承 - 从受保护基类派生时,基类的公有受保护成员成为派生类的受保护成员。

  • 私有继承 - 从私有基类派生时,基类的公有受保护成员成为派生类的私有成员。

多重继承

C++类可以继承多个类的成员,以下是扩展语法:

class derived-class: access baseA, access baseB....

其中,access是public、protectedprivate之一,每个基类都会给出,它们将用逗号分隔,如上所示。

#include <iostream>
 
using namespace std;

// Base class Shape
class Shape {
   public:
      void setWidth(int w) {
         width = w;
      }
      void setHeight(int h) {
         height = h;
      }
      
   protected:
      int width;
      int height;
};

// Base class PaintCost
class PaintCost {
   public:
      int getCost(int area) {
         return area * 70;
      }
};

// Derived class
class Rectangle: public Shape, public PaintCost {
   public:
      int getArea() {
         return (width * height); 
      }
};

int main(void) {
   Rectangle Rect;
   int area;
 
   Rect.setWidth(5);
   Rect.setHeight(7);

   area = Rect.getArea();
   
   // Print the area of the object.
   cout << "Total area: " << Rect.getArea() << endl;

   // Print the total cost of painting
   cout << "Total paint cost: $" << Rect.getCost(area) << endl;

   return 0;
}

编译并执行上述代码时,会产生以下结果:

Total area: 35
Total paint cost: $2450

C++重载(运算符和函数)

C++允许你在同一作用域内为函数名或运算符指定多个定义,这分别称为函数重载运算符重载

重载声明是在同一作用域中声明与先前声明的声明具有相同名称的声明,但两者具有不同的参数,并且显然具有不同的定义(实现)。

调用重载的函数运算符时,编译器会通过将你用于调用函数或运算符的参数类型与定义中指定的参数类型进行比较,来确定最合适的定义。选择最合适的重载函数或运算符的过程称为重载解析

C++中的函数重载

你可以在同一作用域中为同一函数名提供多个定义。函数的定义必须通过参数列表中参数的类型和/或数量来区分。你不能重载仅返回值类型不同的函数声明。

以下示例演示了如何使用相同的函数print()打印不同的数据类型:

#include <iostream>
using namespace std;
 
class printData {
   public:
      void print(int i) {
        cout << "Printing int: " << i << endl;
      }
      void print(double  f) {
        cout << "Printing float: " << f << endl;
      }
      void print(char* c) {
        cout << "Printing character: " << c << endl;
      }
};

int main(void) {
   printData pd;
 
   // Call print to print integer
   pd.print(5);
   
   // Call print to print float
   pd.print(500.263);
   
   // Call print to print character
   pd.print("Hello C++");
 
   return 0;
}

编译并执行上述代码时,会产生以下结果:

Printing int: 5
Printing float: 500.263
Printing character: Hello C++

C++中的运算符重载

你可以重新定义或重载C++中大多数可用的内置运算符。因此,程序员也可以将运算符与用户定义的类型一起使用。

重载运算符是具有特殊名称的函数:关键字“operator”后跟被定义运算符的符号。与任何其他函数一样,重载运算符具有返回类型和参数列表。

Box operator+(const Box&);

声明了可以用来两个Box对象的加法运算符,并返回最终的Box对象。大多数重载运算符可以定义为普通的非成员函数或类成员函数。如果我们将上述函数定义为类的非成员函数,则必须为每个操作数传递两个参数,如下所示:

Box operator+(const Box&, const Box&);

以下示例演示了使用成员函数进行运算符重载的概念。这里将一个对象作为参数传递,其属性将使用此对象访问,调用此运算符的对象可以使用this运算符访问,如下所示:

#include <iostream>
using namespace std;

class Box {
   public:
      double getVolume(void) {
         return length * breadth * height;
      }
      void setLength( double len ) {
         length = len;
      }
      void setBreadth( double bre ) {
         breadth = bre;
      }
      void setHeight( double hei ) {
         height = hei;
      }
      
      // Overload + operator to add two Box objects.
      Box operator+(const Box& b) {
         Box box;
         box.length = this->length + b.length;
         box.breadth = this->breadth + b.breadth;
         box.height = this->height + b.height;
         return box;
      }
      
   private:
      double length;      // Length of a box
      double breadth;     // Breadth of a box
      double height;      // Height of a box
};

// Main function for the program
int main() {
   Box Box1;                // Declare Box1 of type Box
   Box Box2;                // Declare Box2 of type Box
   Box Box3;                // Declare Box3 of type Box
   double volume = 0.0;     // Store the volume of a box here
 
   // box 1 specification
   Box1.setLength(6.0); 
   Box1.setBreadth(7.0); 
   Box1.setHeight(5.0);
 
   // box 2 specification
   Box2.setLength(12.0); 
   Box2.setBreadth(13.0); 
   Box2.setHeight(10.0);
 
   // volume of box 1
   volume = Box1.getVolume();
   cout << "Volume of Box1 : " << volume <<endl;
 
   // volume of box 2
   volume = Box2.getVolume();
   cout << "Volume of Box2 : " << volume <<endl;

   // Add two object as follows:
   Box3 = Box1 + Box2;

   // volume of box 3
   volume = Box3.getVolume();
   cout << "Volume of Box3 : " << volume <<endl;

   return 0;
}

编译并执行上述代码时,会产生以下结果:

Volume of Box1 : 210
Volume of Box2 : 1560
Volume of Box3 : 5400

可重载/不可重载运算符

以下是可重载的运算符列表:

+ - * / % ^
& | ~ ! , =
< > <= >= ++ --
<< >> == != && ||
+= -= /= %= ^= &=
|= *= <<= >>= [] ()
-> ->* new new [] delete delete []

以下是不可重载的运算符列表:

:: .* . ?:

运算符重载示例

以下是一些运算符重载示例,以帮助你理解这个概念。

C++中的多态性

多态性一词意为具有多种形式。通常,当存在类层次结构且它们通过继承相关时,就会发生多态性。

C++多态性意味着对成员函数的调用将导致执行不同的函数,这取决于调用该函数的对象的类型。

考虑以下示例,其中一个基类已被其他两个类派生:

#include <iostream> 
using namespace std;
 
class Shape {
   protected:
      int width, height;
      
   public:
      Shape( int a = 0, int b = 0){
         width = a;
         height = b;
      }
      int area() {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};
class Rectangle: public Shape {
   public:
      Rectangle( int a = 0, int b = 0):Shape(a, b) { }
      
      int area () { 
         cout << "Rectangle class area :" <<endl;
         return (width * height); 
      }
};

class Triangle: public Shape {
   public:
      Triangle( int a = 0, int b = 0):Shape(a, b) { }
      
      int area () { 
         cout << "Triangle class area :" <<endl;
         return (width * height / 2); 
      }
};

// Main function for the program
int main() {
   Shape *shape;
   Rectangle rec(10,7);
   Triangle  tri(10,5);

   // store the address of Rectangle
   shape = &rec;
   
   // call rectangle area.
   shape->area();

   // store the address of Triangle
   shape = &tri;
   
   // call triangle area.
   shape->area();
   
   return 0;
}

编译并执行上述代码时,会产生以下结果:

Parent class area :
Parent class area :

输出不正确的原因是,对函数area()的调用由编译器一次设置为基类中定义的版本。这称为函数调用的静态解析,或静态链接——函数调用在程序执行之前就已固定。这有时也称为早期绑定,因为area()函数是在程序编译期间设置的。

但是现在,让我们对程序进行一些修改,并在Shape类中area()的声明前加上关键字virtual,使其看起来像这样:

class Shape {
   protected:
      int width, height;
      
   public:
      Shape( int a = 0, int b = 0) {
         width = a;
         height = b;
      }
      virtual int area() {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};

经过此细微修改后,当编译并执行前面的示例代码时,它会产生以下结果:

Rectangle class area
Triangle class area

这次,编译器查看指针的内容而不是它的类型。因此,由于tri和rec类的对象的地址存储在*shape中,因此调用相应的area()函数。

正如你所看到的,每个子类都有一个单独的area()函数实现。这就是多态性的通常使用方法。你拥有具有相同名称的函数的不同类,甚至具有相同的参数,但具有不同的实现。

虚函数

虚函数是基类中使用关键字virtual声明的函数。在基类中定义一个虚函数,并在派生类中定义另一个版本,这会向编译器发出信号,表明我们不希望此函数进行静态链接。

我们想要的是在程序的任何给定点上,要调用的函数的选择基于为其调用的对象的类型。这种操作称为动态链接后期绑定

纯虚函数

你可能希望在基类中包含一个虚函数,以便可以在派生类中重新定义它以适应该类的对象,但是对于基类中的函数,你没有可以提供的有意义的定义。

我们可以将基类中的虚函数area()更改为以下内容:

class Shape {
   protected:
      int width, height;

   public:
      Shape(int a = 0, int b = 0) {
         width = a;
         height = b;
      }
      
      // pure virtual function
      virtual int area() = 0;
};

= 0告诉编译器该函数没有主体,上面的虚函数将被称为纯虚函数

C++中的数据抽象

数据抽象是指只向外界提供必要信息,而隐藏其背景细节,即在程序中表示所需信息而不呈现细节。

数据抽象是一种编程(和设计)技术,它依赖于接口和实现的分离。

让我们以电视机为例,你可以打开和关闭它,换台,调节音量,并添加外部组件,例如扬声器、录像机和 DVD 播放器,但是你不知道它的内部细节,也就是说,你不知道它如何接收无线或有线信号,如何转换这些信号,以及最终如何在屏幕上显示它们。

因此,我们可以说电视机清楚地将它的内部实现与其外部接口分隔开来,你可以操作它的接口,例如电源按钮、换台按钮和音量控制,而无需了解其内部结构。

在 C++ 中,类提供了高级别的**数据抽象**。它们向外部世界提供足够的公共方法来使用对象的函数并操作对象数据,即状态,而无需实际了解类的内部实现方式。

例如,你的程序可以调用**sort()**函数,而无需知道该函数实际使用什么算法来对给定值进行排序。事实上,排序功能的底层实现可能会在库的不同版本之间发生变化,只要接口保持不变,你的函数调用仍然有效。

在 C++ 中,我们使用**类**来定义我们自己的抽象数据类型 (ADT)。你可以使用**ostream**类的**cout**对象将数据流式传输到标准输出,如下所示:

#include <iostream>
using namespace std;

int main() {
   cout << "Hello C++" <<endl;
   return 0;
}

在这里,你不需要了解**cout**如何将文本显示在用户的屏幕上。你只需要知道公共接口,而‘cout’的底层实现可以随意更改。

访问标签强制执行抽象

在 C++ 中,我们使用访问标签来定义类的抽象接口。一个类可以包含零个或多个访问标签:

  • 使用公共标签定义的成员可以被程序的所有部分访问。类型的抽象数据视图由其公共成员定义。

  • 使用私有标签定义的成员无法被使用该类的代码访问。私有部分隐藏了使用该类型的代码的实现。

访问标签出现的频率没有限制。每个访问标签都指定后续成员定义的访问级别。指定的访问级别一直有效,直到遇到下一个访问标签或类的主体右括号。

数据抽象的优点

数据抽象提供了两个重要的优点:

  • 类内部受到保护,免受无意的用户级错误的影响,这些错误可能会破坏对象的状态。

  • 类实现可以随着时间的推移而发展,以响应不断变化的需求或错误报告,而无需更改用户级代码。

通过仅在类的私有部分定义数据成员,类作者可以自由地更改数据。如果实现发生更改,只需要检查类代码即可查看更改可能产生的影响。如果数据是公共的,那么任何直接访问旧表示的数据成员的函数都可能被破坏。

数据抽象示例

任何在其中实现具有公共和私有成员的类的 C++ 程序都是数据抽象的示例。考虑以下示例:

#include <iostream>
using namespace std;

class Adder {
   public:
      // constructor
      Adder(int i = 0) {
         total = i;
      }
      
      // interface to outside world
      void addNum(int number) {
         total += number;
      }
      
      // interface to outside world
      int getTotal() {
         return total;
      };
      
   private:
      // hidden data from outside world
      int total;
};

int main() {
   Adder a;
   
   a.addNum(10);
   a.addNum(20);
   a.addNum(30);

   cout << "Total " << a.getTotal() <<endl;
   return 0;
}

编译并执行上述代码时,会产生以下结果:

Total 60

上面的类将数字加在一起,并返回总和。公共成员 - **addNum** 和 **getTotal** 是面向外部世界的接口,用户需要知道它们才能使用该类。私有成员 **total** 是用户不需要了解的内容,但它是类正常运行所必需的。

设计策略

抽象将代码分为接口和实现。因此,在设计组件时,必须保持接口与实现的独立性,以便如果更改底层实现,接口将保持不变。

在这种情况下,无论使用这些接口的程序是什么,它们都不会受到影响,只需要使用最新的实现重新编译即可。

C++ 中的数据封装

所有 C++ 程序都由以下两个基本元素组成:

  • **程序语句(代码)** - 这是程序执行操作的部分,它们被称为函数。

  • **程序数据** - 数据是程序的信息,受程序函数的影响。

封装是一个面向对象编程的概念,它将数据和操作数据的函数绑定在一起,并使两者都免受外部干扰和误用的影响。数据封装导致了重要的面向对象编程概念 **数据隐藏**。

**数据封装**是一种将数据及其使用的函数捆绑在一起的机制,而**数据抽象**是一种仅公开接口并向用户隐藏实现细节的机制。

C++ 通过创建称为**类**的用户定义类型来支持封装和数据隐藏的特性。我们已经学习过一个类可以包含**私有、保护**和**公共**成员。默认情况下,在类中定义的所有项目都是私有的。例如:

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

   private:
      double length;      // Length of a box
      double breadth;     // Breadth of a box
      double height;      // Height of a box
};

变量 length、breadth 和 height 是**私有的**。这意味着它们只能被 Box 类的其他成员访问,而不能被程序的任何其他部分访问。这是实现封装的一种方式。

要使类的部分成员成为**公共的**(即,可被程序的其他部分访问),必须在**public**关键字之后声明它们。在 public 说明符之后定义的所有变量或函数都可以被程序中的所有其他函数访问。

使一个类成为另一个类的 friend 会暴露实现细节并降低封装性。理想的做法是尽可能隐藏每个类的细节,使其不被其他任何类访问。

数据封装示例

任何在其中实现具有公共和私有成员的类的 C++ 程序都是数据封装和数据抽象的示例。考虑以下示例:

#include <iostream>
using namespace std;

class Adder {
   public:
      // constructor
      Adder(int i = 0) {
         total = i;
      }
      
      // interface to outside world
      void addNum(int number) {
         total += number;
      }
      
      // interface to outside world
      int getTotal() {
         return total;
      };
   
   private:
      // hidden data from outside world
      int total;
};

int main() {
   Adder a;
   
   a.addNum(10);
   a.addNum(20);
   a.addNum(30);

   cout << "Total " << a.getTotal() <<endl;
   return 0;
}

编译并执行上述代码时,会产生以下结果:

Total 60

上面的类将数字加在一起,并返回总和。公共成员 **addNum** 和 **getTotal** 是面向外部世界的接口,用户需要知道它们才能使用该类。私有成员 **total** 是对外部世界隐藏的内容,但它是类正常运行所必需的。

设计策略

我们大多数人都学会了默认情况下将类成员设为私有,除非我们确实需要公开它们。这只是良好的**封装**。

这最常应用于数据成员,但也同样适用于所有成员,包括虚函数。

C++ 中的接口(抽象类)

接口描述了 C++ 类的行为或功能,而无需采用该类的特定实现。

C++ 接口是使用**抽象类**实现的,这些抽象类不应与数据抽象混淆,数据抽象是将实现细节与相关数据分开的概念。

通过至少声明其一个函数为**纯虚函数**来使类成为抽象类。纯虚函数通过在其声明中放置“= 0”来指定,如下所示:

class Box {
   public:
      // pure virtual function
      virtual double getVolume() = 0;
      
   private:
      double length;      // Length of a box
      double breadth;     // Breadth of a box
      double height;      // Height of a box
};

**抽象类**(通常称为 ABC) 的目的是提供一个合适的基类,其他类可以从中继承。抽象类不能用于实例化对象,仅用作**接口**。尝试实例化抽象类的对象会导致编译错误。

因此,如果 ABC 的子类需要被实例化,它必须实现每个虚函数,这意味着它支持 ABC 声明的接口。如果在派生类中没有重写纯虚函数,则尝试实例化该类的对象将导致编译错误。

可以用来实例化对象的类称为**具体类**。

抽象类示例

考虑以下示例,其中父类提供了一个接口,供基类实现名为 **getArea()** 的函数:

#include <iostream>
 
using namespace std;
 
// Base class
class Shape {
   public:
      // pure virtual function providing interface framework.
      virtual int getArea() = 0;
      void setWidth(int w) {
         width = w;
      }
   
      void setHeight(int h) {
         height = h;
      }
   
   protected:
      int width;
      int height;
};
 
// Derived classes
class Rectangle: public Shape {
   public:
      int getArea() { 
         return (width * height); 
      }
};

class Triangle: public Shape {
   public:
      int getArea() { 
         return (width * height)/2; 
      }
};
 
int main(void) {
   Rectangle Rect;
   Triangle  Tri;
 
   Rect.setWidth(5);
   Rect.setHeight(7);
   
   // Print the area of the object.
   cout << "Total Rectangle area: " << Rect.getArea() << endl;

   Tri.setWidth(5);
   Tri.setHeight(7);
   
   // Print the area of the object.
   cout << "Total Triangle area: " << Tri.getArea() << endl; 

   return 0;
}

编译并执行上述代码时,会产生以下结果:

Total Rectangle area: 35
Total Triangle area: 17

您可以看到抽象类如何根据 getArea() 定义接口,以及其他两个类如何实现相同的函数,但使用不同的算法来计算特定形状的面积。

设计策略

面向对象的系统可以使用抽象基类来提供适合所有外部应用程序的通用且标准化的接口。然后,通过从该抽象基类继承,形成操作方式相似的派生类。

外部应用程序提供的功能(即公共函数)在抽象基类中作为纯虚函数提供。这些纯虚函数的实现在对应于应用程序特定类型的派生类中提供。

这种架构还允许轻松地将新应用程序添加到系统中,即使在定义系统之后也是如此。

C++ 文件和流

到目前为止,我们一直在使用 **iostream** 标准库,该库提供 **cin** 和 **cout** 方法,分别用于从标准输入读取和写入标准输出。

本教程将教你如何从文件读取和写入。这需要另一个名为 **fstream** 的标准 C++ 库,它定义了三个新的数据类型:

序号 数据类型和描述
1

ofstream

此数据类型表示输出文件流,用于创建文件并将信息写入文件。

2

ifstream

此数据类型表示输入文件流,用于从文件读取信息。

3

fstream

此数据类型通常表示文件流,并且具有 ofstream 和 ifstream 的功能,这意味着它可以创建文件、将信息写入文件和从文件读取信息。

要在 C++ 中执行文件处理,必须在 C++ 源文件中包含头文件 <iostream> 和 <fstream>。

打开文件

在读取或写入文件之前,必须先打开文件。可以使用 **ofstream** 或 **fstream** 对象打开要写入的文件。而 ifstream 对象仅用于打开要读取的文件。

以下是 open() 函数的标准语法,它是 fstream、ifstream 和 ofstream 对象的成员。

void open(const char *filename, ios::openmode mode);

这里,第一个参数指定要打开的文件的名称和位置,**open()** 成员函数的第二个参数定义应以何种模式打开文件。

序号 模式标志和描述
1

ios::app

追加模式。所有写入该文件的内容都将追加到文件的末尾。

2

ios::ate

打开一个用于输出的文件,并将读/写控制移动到文件的末尾。

3

ios::in

打开一个用于读取的文件。

4

ios::out

打开一个用于写入的文件。

5

ios::trunc

如果文件已存在,则在打开文件之前将截断其内容。

可以通过将它们**OR**在一起来组合两个或多个这些值。例如,如果要以写入模式打开文件,并且如果文件已存在,则要截断它,则语法如下:

ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc );

类似地,可以打开一个用于读取和写入的文件,如下所示:

fstream  afile;
afile.open("file.dat", ios::out | ios::in );

关闭文件

当 C++ 程序终止时,它会自动刷新所有流,释放所有已分配的内存并关闭所有打开的文件。但程序员在程序终止前关闭所有打开的文件始终是一个好习惯。

以下是 close() 函数的标准语法,它是 fstream、ifstream 和 ofstream 对象的成员。

void close();

写入文件

在进行 C++ 编程时,您可以使用流插入运算符(<<)将信息从程序写入文件,就像使用该运算符将信息输出到屏幕一样。唯一的区别在于您使用的是ofstreamfstream 对象而不是 cout 对象。

从文件读取

您可以使用流提取运算符(>>)将信息从文件读取到程序中,就像使用该运算符从键盘输入信息一样。唯一的区别在于您使用的是ifstreamfstream 对象而不是 cin 对象。

读写示例

以下是 C++ 程序,它以读写模式打开文件。该程序将用户输入的信息写入名为 afile.dat 的文件后,会从文件中读取信息并将其输出到屏幕上:

#include <fstream>
#include <iostream>
using namespace std;
 
int main () {
   char data[100];

   // open a file in write mode.
   ofstream outfile;
   outfile.open("afile.dat");

   cout << "Writing to the file" << endl;
   cout << "Enter your name: "; 
   cin.getline(data, 100);

   // write inputted data into the file.
   outfile << data << endl;

   cout << "Enter your age: "; 
   cin >> data;
   cin.ignore();
   
   // again write inputted data into the file.
   outfile << data << endl;

   // close the opened file.
   outfile.close();

   // open a file in read mode.
   ifstream infile; 
   infile.open("afile.dat"); 
 
   cout << "Reading from the file" << endl; 
   infile >> data; 

   // write the data at the screen.
   cout << data << endl;
   
   // again read the data from the file and display it.
   infile >> data; 
   cout << data << endl; 

   // close the opened file.
   infile.close();

   return 0;
}

编译并执行上述代码后,会生成以下示例输入和输出:

$./a.out
Writing to the file
Enter your name: Zara
Enter your age: 9
Reading from the file
Zara
9

上述示例使用了 cin 对象的附加函数,例如 getline() 函数从外部读取行,以及 ignore() 函数忽略先前读取语句留下的额外字符。

文件位置指针

istreamostream 都提供用于重新定位文件位置指针的成员函数。这些成员函数是 seekg(“seek get”)用于 istream,seekp(“seek put”)用于 ostream。

seekg 和 seekp 的参数通常是长整数。可以指定第二个参数来指示搜索方向。搜索方向可以是ios::beg(默认值),用于相对于流的开头定位;ios::cur,用于相对于流中的当前位置定位;或ios::end,用于相对于流的末尾定位。

文件位置指针是一个整数值,它指定文件中距文件起始位置的字节数位置。一些定位“get”文件位置指针的示例如下:

// position to the nth byte of fileObject (assumes ios::beg)
fileObject.seekg( n );

// position n bytes forward in fileObject
fileObject.seekg( n, ios::cur );

// position n bytes back from end of fileObject
fileObject.seekg( n, ios::end );

// position at end of fileObject
fileObject.seekg( 0, ios::end );

C++ 异常处理

异常是在程序执行期间出现的错误。C++ 异常是对程序运行时出现的异常情况的响应,例如尝试除以零。

异常提供了一种将控制从程序的某个部分转移到另一个部分的方法。C++ 异常处理基于三个关键字:try、catchthrow

  • throw − 当出现问题时,程序会抛出异常。这是使用throw关键字完成的。

  • catch − 程序在程序中要处理问题的某个位置使用异常处理程序捕获异常。catch关键字表示捕获异常。

  • trytry 块标识将为其激活特定异常的代码块。它后面跟有一个或多个 catch 块。

假设一个块会引发异常,则一个方法使用trycatch关键字的组合来捕获异常。try/catch 块放置在可能生成异常的代码周围。try/catch 块中的代码称为受保护代码,使用 try/catch 的语法如下:

try {
   // protected code
} catch( ExceptionName e1 ) {
   // catch block
} catch( ExceptionName e2 ) {
   // catch block
} catch( ExceptionName eN ) {
   // catch block
}

您可以列出多个catch语句来捕获不同类型的异常,以防您的try块在不同情况下引发多个异常。

抛出异常

可以使用throw语句在代码块中的任何位置抛出异常。throw 语句的操作数确定异常的类型,可以是任何表达式,表达式的结果类型决定了抛出的异常类型。

以下是在发生除以零条件时抛出异常的示例:

double division(int a, int b) {
   if( b == 0 ) {
      throw "Division by zero condition!";
   }
   return (a/b);
}

捕获异常

try块后面的catch块捕获任何异常。您可以指定要捕获的异常类型,这由出现在 catch 关键字后面的括号中的异常声明决定。

try {
   // protected code
} catch( ExceptionName e ) {
  // code to handle ExceptionName exception
}

上面的代码将捕获ExceptionName类型的异常。如果要指定 catch 块应处理 try 块中抛出的任何类型的异常,则必须在包含异常声明的括号之间放置省略号 ...,如下所示:

try {
   // protected code
} catch(...) {
  // code to handle any exception
}

以下是一个示例,它抛出除以零异常,我们在 catch 块中捕获它。

#include <iostream>
using namespace std;

double division(int a, int b) {
   if( b == 0 ) {
      throw "Division by zero condition!";
   }
   return (a/b);
}

int main () {
   int x = 50;
   int y = 0;
   double z = 0;
 
   try {
      z = division(x, y);
      cout << z << endl;
   } catch (const char* msg) {
     cerr << msg << endl;
   }

   return 0;
}

因为我们抛出的异常类型是const char*,所以在捕获此异常时,我们必须在 catch 块中使用 const char*。如果我们编译并运行上面的代码,则会产生以下结果:

Division by zero condition!

C++ 标准异常

C++ 提供了在<exception>中定义的标准异常列表,我们可以在程序中使用。这些异常按如下所示的父子类层次结构排列:

C++ Exceptions Hierarchy

以下是上述层次结构中每个异常的简短描述:

序号 异常和描述
1

std::exception

一个异常,也是所有标准 C++ 异常的父类。

2

std::bad_alloc

这可能由new抛出。

3

std::bad_cast

这可能由dynamic_cast抛出。

4

std::bad_exception

这是一个有用的工具,用于处理 C++ 程序中的意外异常。

5

std::bad_typeid

这可能由typeid抛出。

6

std::logic_error

理论上可以通过阅读代码检测到的异常。

7

std::domain_error

当使用数学上无效的域时抛出的异常。

8

std::invalid_argument

由于参数无效而抛出。

9

std::length_error

创建太大的 std::string 时抛出。

10

std::out_of_range

例如,这可能由'at'方法(例如 std::vector 和 std::bitset<>::operator[]())抛出。

11

std::runtime_error

理论上无法通过阅读代码检测到的异常。

12

std::overflow_error

如果发生数学溢出,则会抛出此异常。

13

std::range_error

当您尝试存储超出范围的值时发生。

14

std::underflow_error

如果发生数学下溢,则会抛出此异常。

定义新的异常

您可以通过继承和重写exception类的功能来定义自己的异常。以下是一个示例,它展示了如何使用 std::exception 类以标准方式实现您自己的异常:

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

struct MyException : public exception {
   const char * what () const throw () {
      return "C++ Exception";
   }
};
 
int main() {
   try {
      throw MyException();
   } catch(MyException& e) {
      std::cout << "MyException caught" << std::endl;
      std::cout << e.what() << std::endl;
   } catch(std::exception& e) {
      //Other errors
   }
}

这将产生以下结果:

MyException caught
C++ Exception

这里,what() 是 exception 类提供的公共方法,并且所有子异常类都已重写它。这返回异常的原因。

C++ 动态内存

充分了解 C++ 中动态内存的实际工作方式对于成为优秀的 C++ 程序员至关重要。C++ 程序中的内存分为两部分:

  • − 在函数内部声明的所有变量都将占用来自栈的内存。

  • − 这是程序的未用内存,可以在程序运行时动态分配内存。

很多时候,您事先并不知道需要多少内存来存储定义变量中的特定信息,并且所需内存的大小可以在运行时确定。

您可以使用 C++ 中的特殊运算符为给定类型的变量在堆中运行时分配内存,该运算符返回已分配空间的地址。此运算符称为new运算符。

如果您不再需要动态分配的内存,则可以使用delete运算符,该运算符将取消先前由 new 运算符分配的内存。

new 和 delete 运算符

以下是用new运算符动态分配任何数据类型内存的通用语法。

new data-type;

这里,数据类型可以是任何内置数据类型,包括数组或任何用户定义的数据类型,包括类或结构。让我们从内置数据类型开始。例如,我们可以定义一个指向 double 类型的指针,然后请求在执行时分配内存。我们可以使用new运算符和以下语句来执行此操作:

double* pvalue  = NULL; // Pointer initialized with null
pvalue  = new double;   // Request memory for the variable

如果空闲存储已用完,则可能无法成功分配内存。因此,最好检查 new 运算符是否返回 NULL 指针并采取适当的措施,如下所示:

double* pvalue  = NULL;
if( !(pvalue  = new double )) {
   cout << "Error: out of memory." <<endl;
   exit(1);
}

来自 C 的malloc()函数仍然存在于 C++ 中,但建议避免使用 malloc() 函数。new 相对于 malloc() 的主要优点是 new 不仅仅分配内存,它还构造对象,这是 C++ 的主要目的。

在任何时候,当您觉得不再需要动态分配的变量时,您可以使用“delete”运算符释放它在空闲存储中占用的内存,如下所示:

delete pvalue;        // Release memory pointed to by pvalue

让我们将上述概念组合起来,形成以下示例以显示“new”和“delete”的工作方式:

#include <iostream>
using namespace std;

int main () {
   double* pvalue  = NULL; // Pointer initialized with null
   pvalue  = new double;   // Request memory for the variable
 
   *pvalue = 29494.99;     // Store value at allocated address
   cout << "Value of pvalue : " << *pvalue << endl;

   delete pvalue;         // free up the memory.

   return 0;
}

如果我们编译并运行上面的代码,这将产生以下结果:

Value of pvalue : 29495

数组的动态内存分配

假设您想为字符数组(即 20 个字符的字符串)分配内存。使用我们上面使用的相同语法,我们可以动态分配内存,如下所示。

char* pvalue  = NULL;         // Pointer initialized with null
pvalue  = new char[20];       // Request memory for the variable

要删除我们刚刚创建的数组,语句将如下所示:

delete [] pvalue;             // Delete array pointed to by pvalue

按照 new 运算符的类似通用语法,您可以为多维数组分配内存,如下所示:

double** pvalue  = NULL;      // Pointer initialized with null 
pvalue  = new double [3][4];  // Allocate memory for a 3x4 array 

但是,释放多维数组内存的语法仍然与上面相同:

delete [] pvalue;            // Delete array pointed to by pvalue

对象的动态内存分配

对象与简单数据类型没有什么不同。例如,考虑以下代码,我们将使用对象数组来阐明这个概念:

#include <iostream>
using namespace std;

class Box {
   public:
      Box() { 
         cout << "Constructor called!" <<endl; 
      }
      ~Box() { 
         cout << "Destructor called!" <<endl; 
      }
};
int main() {
   Box* myBoxArray = new Box[4];
   delete [] myBoxArray; // Delete array

   return 0;
}

如果您要分配四个 Box 对象的数组,则 Simple 构造函数将被调用四次,同样,在删除这些对象时,析构函数也将被调用相同次数。

如果我们编译并运行上面的代码,这将产生以下结果:

Constructor called!
Constructor called!
Constructor called!
Constructor called!
Destructor called!
Destructor called!
Destructor called!
Destructor called!

C++ 中的命名空间

考虑这种情况,当我们有两个同名的人,Zara,在同一个班级。每当我们需要区分他们时,我们肯定必须使用一些附加信息以及他们的姓名,例如他们居住的地区(如果他们居住在不同地区)或他们母亲或父亲的姓名等。

同样的情况也可能出现在您的 C++ 应用程序中。例如,您可能正在编写一些包含名为 xyz() 的函数的代码,并且还有一个也包含相同函数 xyz() 的可用库。现在,编译器无法知道您在代码中引用的是哪个版本的 xyz() 函数。

命名空间旨在克服这种困难,并用作附加信息来区分在不同库中可用的名称相同的类似函数、类、变量等。使用命名空间,您可以定义定义名称的上下文。本质上,命名空间定义了一个范围。

定义命名空间

命名空间定义以关键字namespace开头,后跟命名空间名称,如下所示:

namespace namespace_name {
   // code declarations
}

要调用函数或变量的启用命名空间的版本,请在前面添加 (::) 命名空间名称,如下所示:

name::code;  // code could be variable or function.

让我们看看命名空间如何作用域实体,包括变量和函数:

#include <iostream>
using namespace std;

// first name space
namespace first_space {
   void func() {
      cout << "Inside first_space" << endl;
   }
}

// second name space
namespace second_space {
   void func() {
      cout << "Inside second_space" << endl;
   }
}

int main () {
   // Calls function from first name space.
   first_space::func();
   
   // Calls function from second name space.
   second_space::func(); 

   return 0;
}

如果我们编译并运行上面的代码,这将产生以下结果:

Inside first_space
Inside second_space

using 指令

您也可以使用 `using namespace` 指令避免命名空间的前缀。此指令告诉编译器后续代码正在使用指定命名空间中的名称。因此,命名空间将隐式应用于以下代码。

#include <iostream>
using namespace std;

// first name space
namespace first_space {
   void func() {
      cout << "Inside first_space" << endl;
   }
}

// second name space
namespace second_space {
   void func() {
      cout << "Inside second_space" << endl;
   }
}

using namespace first_space;
int main () {
   // This calls function from first name space.
   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;
}

如果我们编译并运行上面的代码,这将产生以下结果:

std::endl is used with std!

在 `using` 指令中引入的名称遵循正常的范围规则。该名称从 `using` 指令的位置到找到该指令的范围的末尾都是可见的。在外层作用域中定义的同名实体将被隐藏。

不连续命名空间

命名空间可以分为几个部分,因此命名空间由其各个单独定义的部分的总和构成。命名空间的各个部分可以分布在多个文件中。

因此,如果命名空间的一部分需要在另一个文件中定义的名称,则该名称仍然必须声明。编写以下命名空间定义,要么定义一个新的命名空间,要么向现有命名空间添加新元素:

namespace namespace_name {
   // code declarations
}

嵌套命名空间

命名空间可以嵌套,您可以按如下方式在一个命名空间内定义另一个命名空间:

namespace namespace_name1 {
   // code declarations
   namespace namespace_name2 {
      // code declarations
   }
}

您可以使用解析运算符访问嵌套命名空间的成员,如下所示:

// to access members of namespace_name2
using namespace namespace_name1::namespace_name2;

// to access members of namespace:name1
using namespace namespace_name1;

在上例中,如果您使用 `namespace_name1`,它将使 `namespace_name2` 的元素在作用域中可用,如下所示:

#include <iostream>
using namespace std;

// first name space
namespace first_space {
   void func() {
      cout << "Inside first_space" << endl;
   }
   
   // second name space
   namespace second_space {
      void func() {
         cout << "Inside second_space" << endl;
      }
   }
}

using namespace first_space::second_space;
int main () {
   // This calls function from second name space.
   func();
   
   return 0;
}

如果我们编译并运行上面的代码,这将产生以下结果:

Inside second_space

C++ 模板

模板是泛型编程的基础,泛型编程涉及以独立于任何特定类型的方式编写代码。

模板是创建泛型类或函数的蓝图或公式。像迭代器和算法这样的库容器是泛型编程的示例,并且是使用模板概念开发的。

每个容器(例如 `vector`)只有一个定义,但我们可以定义许多不同类型的向量,例如 `vector <int>` 或 `vector <string>`。

您可以使用模板定义函数和类,让我们看看它们是如何工作的:

函数模板

此处显示模板函数定义的一般形式:

template <class type> ret-type func-name(parameter list) {
   // body of function
} 

这里,`type` 是函数使用的数据类型的占位符名称。此名称可以在函数定义中使用。

以下是一个返回两个值最大值的函数模板示例:

#include <iostream>
#include <string>

using namespace std;

template <typename T>
inline T const& Max (T const& a, T const& b) { 
   return a < b ? b:a; 
}

int main () {
   int i = 39;
   int j = 20;
   cout << "Max(i, j): " << Max(i, j) << endl; 

   double f1 = 13.5; 
   double f2 = 20.7; 
   cout << "Max(f1, f2): " << Max(f1, f2) << endl; 

   string s1 = "Hello"; 
   string s2 = "World"; 
   cout << "Max(s1, s2): " << Max(s1, s2) << endl; 

   return 0;
}

如果我们编译并运行上面的代码,这将产生以下结果:

Max(i, j): 39
Max(f1, f2): 20.7
Max(s1, s2): World

类模板

就像我们可以定义函数模板一样,我们也可以定义类模板。此处显示泛型类声明的一般形式:

template <class type> class class-name {
   .
   .
   .
}

这里,`type` 是占位符类型名称,在实例化类时将指定它。您可以使用逗号分隔的列表定义多个泛型数据类型。

以下是如何定义类 `Stack<>` 并实现泛型方法来压入和弹出栈中元素的示例:

#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>

using namespace std;

template <class T>
class Stack { 
   private: 
      vector<T> elems;    // elements 

   public: 
      void push(T const&);  // push element 
      void pop();               // pop element 
      T top() const;            // return top element 
      
      bool empty() const {      // return true if empty.
         return elems.empty(); 
      } 
}; 

template <class T>
void Stack<T>::push (T const& elem) { 
   // append copy of passed element 
   elems.push_back(elem);    
} 

template <class T>
void Stack<T>::pop () { 
   if (elems.empty()) { 
      throw out_of_range("Stack<>::pop(): empty stack"); 
   }
   
   // remove last element 
   elems.pop_back();         
} 

template <class T>
T Stack<T>::top () const { 
   if (elems.empty()) { 
      throw out_of_range("Stack<>::top(): empty stack"); 
   }
   
   // return copy of last element 
   return elems.back();      
} 

int main() { 
   try {
      Stack<int>         intStack;  // stack of ints 
      Stack<string> stringStack;    // stack of strings 

      // manipulate int stack 
      intStack.push(7); 
      cout << intStack.top() <<endl; 

      // manipulate string stack 
      stringStack.push("hello"); 
      cout << stringStack.top() << std::endl; 
      stringStack.pop(); 
      stringStack.pop(); 
   } catch (exception const& ex) { 
      cerr << "Exception: " << ex.what() <<endl; 
      return -1;
   } 
} 

如果我们编译并运行上面的代码,这将产生以下结果:

7
hello
Exception: Stack<>::pop(): empty stack

C++ 预处理器

预处理器是指令,它们指示编译器在实际编译开始之前预处理信息。

所有预处理器指令都以 `#` 开头,一行预处理器指令之前只能出现空格字符。预处理器指令不是 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;
}

现在,让我们对这段代码进行预处理以查看结果,假设我们有源代码文件。因此,让我们使用 `-E` 选项编译它并将结果重定向到 `test.p`。现在,如果您检查 `test.p`,它将包含大量信息,并且在底部,您会发现值已替换为:

$gcc -E test.cpp > test.p

...
int main () {
   cout << "Value of PI :" << 3.14159 << 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 <<"The minimum is " << MIN(i, j) << endl;

   return 0;
}

如果我们编译并运行上面的代码,这将产生以下结果:

The minimum is 30

条件编译

有几个指令可以用于编译程序源代码的某些部分。此过程称为条件编译。

条件预处理器结构很像 `if` 选择结构。考虑以下预处理器代码:

#ifndef NULL
   #define NULL 0
#endif

您可以编译程序以进行调试。您还可以使用单个宏打开或关闭调试,如下所示:

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

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

#if 0
   code prevented from compiling
#endif

让我们尝试以下示例:

#include <iostream>
using namespace std;
#define DEBUG

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

int main () {
   int i, j;
   
   i = 100;
   j = 30;

#ifdef DEBUG
   cerr <<"Trace: Inside main function" << endl;
#endif

#if 0
   /* This is commented part */
   cout << MKSTR(HELLO C++) << endl;
#endif

   cout <<"The minimum is " << MIN(i, j) << endl;

#ifdef DEBUG
   cerr <<"Trace: Coming out of main function" << endl;
#endif

   return 0;
}

如果我们编译并运行上面的代码,这将产生以下结果:

The minimum is 30
Trace: Inside main function
Trace: Coming out of main function

`#` 和 `##` 运算符

`#` 和 `##` 预处理器运算符在 C++ 和 ANSI/ISO C 中可用。 `#` 运算符会导致将替换文本标记转换为用引号括起来的字符串。

考虑以下宏定义:

#include <iostream>
using namespace std;

#define MKSTR( x ) #x

int main () {

   cout << MKSTR(HELLO C++) << endl;

   return 0;
}

如果我们编译并运行上面的代码,这将产生以下结果:

HELLO C++

让我们看看它是如何工作的。很容易理解,C++ 预处理器将以下行:

cout << MKSTR(HELLO C++) << endl;

转换为以下行:

cout << "HELLO C++" << endl;

`##` 运算符用于连接两个标记。这是一个例子:

#define CONCAT( x, y )  x ## y

当 `CONCAT` 出现在程序中时,它的参数将被连接起来并用于替换宏。例如,`CONCAT(HELLO, C++)` 在程序中将替换为 `"HELLO C++"`,如下所示。

#include <iostream>
using namespace std;

#define concat(a, b) a ## b
int main() {
   int xy = 100;
   
   cout << concat(x, y);
   return 0;
}

如果我们编译并运行上面的代码,这将产生以下结果:

100

让我们看看它是如何工作的。很容易理解,C++ 预处理器将以下行:

cout << concat(x, y);

转换为以下行:

cout << xy;

预定义的 C++ 宏

C++ 提供了许多下面提到的预定义宏:

序号 宏 & 说明
1

__LINE__

这包含程序在编译时的当前行号。

2

__FILE__

这包含程序在编译时的当前文件名。

3

__DATE__

这包含一个格式为月/日/年的字符串,它是源文件转换为目标代码的日期。

4

__TIME__

这包含一个格式为时:分:秒的字符串,它是编译程序的时间。

让我们看看所有上述宏的一个例子:

#include <iostream>
using namespace std;

int main () {
   cout << "Value of __LINE__ : " << __LINE__ << endl;
   cout << "Value of __FILE__ : " << __FILE__ << endl;
   cout << "Value of __DATE__ : " << __DATE__ << endl;
   cout << "Value of __TIME__ : " << __TIME__ << endl;

   return 0;
}

如果我们编译并运行上面的代码,这将产生以下结果:

Value of __LINE__ : 6
Value of __FILE__ : test.cpp
Value of __DATE__ : Feb 28 2011
Value of __TIME__ : 18:52:48

C++ 信号处理

信号是由操作系统传递给进程的中断,它可以过早地终止程序。您可以通过在 UNIX、LINUX、Mac OS X 或 Windows 系统上按 Ctrl+C 来生成中断。

有一些信号程序无法捕获,但有一份列表列出了您可以在程序中捕获的信号,并且可以根据信号采取适当的操作。这些信号在 C++ 头文件 `` 中定义。

序号 信号 & 说明
1

SIGABRT

程序异常终止,例如调用 `abort`。

2

SIGFPE

错误的算术运算,例如被零除或导致溢出的运算。

3

SIGILL

检测到非法指令。

4

SIGINT

收到交互式注意信号。

5

SIGSEGV

无效的存储器访问。

6

SIGTERM

发送到程序的终止请求。

signal() 函数

C++ 信号处理库提供 `signal` 函数来捕获意外事件。以下是 `signal()` 函数的语法:

void (*signal (int sig, void (*func)(int)))(int); 

简而言之,此函数接收两个参数:第一个参数是一个整数,表示信号编号;第二个参数是指向信号处理函数的指针。

让我们编写一个简单的 C++ 程序,我们将在其中使用 `signal()` 函数捕获 `SIGINT` 信号。您要在程序中捕获任何信号,都必须使用 `signal` 函数注册该信号并将其与信号处理程序关联。检查以下示例:

#include <iostream>
#include <csignal>

using namespace std;

void signalHandler( int signum ) {
   cout << "Interrupt signal (" << signum << ") received.\n";

   // cleanup and close up stuff here  
   // terminate program  

   exit(signum);  
}

int main () {
   // register signal SIGINT and signal handler  
   signal(SIGINT, signalHandler);  

   while(1) {
      cout << "Going to sleep...." << endl;
      sleep(1);
   }

   return 0;
}

编译并执行上述代码时,会产生以下结果:

Going to sleep....
Going to sleep....
Going to sleep....

现在,按 Ctrl+c 中断程序,您将看到您的程序将捕获信号并通过打印如下内容退出:

Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.

raise() 函数

您可以通过 `raise()` 函数生成信号,该函数以整数信号编号作为参数,并具有以下语法。

int raise (signal sig);

这里,`sig` 是要发送的信号编号,可以是以下任何信号:`SIGINT`、`SIGABRT`、`SIGFPE`、`SIGILL`、`SIGSEGV`、`SIGTERM`、`SIGHUP`。以下是一个使用 `raise()` 函数在内部引发信号的示例:

#include <iostream>
#include <csignal>

using namespace std;

void signalHandler( int signum ) {
   cout << "Interrupt signal (" << signum << ") received.\n";

   // cleanup and close up stuff here  
   // terminate program  

   exit(signum);  
}

int main () {
   int i = 0;
   // register signal SIGINT and signal handler  
   signal(SIGINT, signalHandler);  

   while(++i) {
      cout << "Going to sleep...." << endl;
      if( i == 3 ) {
         raise( SIGINT);
      }
      sleep(1);
   }

   return 0;
}

编译并执行上述代码时,它会产生以下结果并自动退出:

Going to sleep....
Going to sleep....
Going to sleep....
Interrupt signal (2) received.

C++ 多线程

多线程是多任务处理的一种特殊形式,多任务处理允许您的计算机同时运行两个或多个程序。一般来说,多任务处理有两种类型:基于进程的和基于线程的。

基于进程的多任务处理处理程序的并发执行。基于线程的多任务处理处理同一程序的各个部分的并发执行。

多线程程序包含两个或多个可以并发运行的部分。此类程序的每一部分称为一个线程,每个线程定义一个单独的执行路径。

在 C++ 11 之前,没有对多线程应用程序的内置支持。相反,它完全依赖于操作系统来提供此功能。

本教程假设您正在 Linux 操作系统上工作,我们将使用 POSIX 编写多线程 C++ 程序。POSIX 线程或 Pthreads 提供了可在许多类 Unix POSIX 系统(如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris)上使用的 API。

创建线程

以下例程用于创建 POSIX 线程:

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg) 

此处,pthread_create 创建一个新线程并使其可执行。此例程可以从代码中的任何位置调用任意多次。以下是参数的描述:

序号 参数及描述
1

thread

子例程返回的新的线程的不透明、唯一标识符。

2

attr

一个不透明的属性对象,可用于设置线程属性。您可以指定一个线程属性对象,或者使用 NULL 表示默认值。

3

start_routine

线程创建后将执行的 C++ 例程。

4

arg

可以传递给 start_routine 的单个参数。它必须通过引用作为 void 类型指针的强制转换来传递。如果不需要传递参数,可以使用 NULL。

进程可以创建的线程最大数量取决于实现。创建后,线程是同等地位的,可以创建其他线程。线程之间没有隐含的层次结构或依赖关系。

终止线程

我们使用以下例程来终止 POSIX 线程:

#include <pthread.h>
pthread_exit (status) 

这里pthread_exit 用于显式退出线程。通常,在线程完成其工作并且不再需要存在后,会调用 pthread_exit() 例程。

如果 main() 在其创建的线程之前完成并使用 pthread_exit() 退出,则其他线程将继续执行。否则,当 main() 完成时,它们将自动终止。

示例

此简单的示例代码使用 pthread_create() 例程创建 5 个线程。每个线程打印一条“Hello World!”消息,然后通过调用 pthread_exit() 终止。

#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

void *PrintHello(void *threadid) {
   long tid;
   tid = (long)threadid;
   cout << "Hello World! Thread ID, " << tid << endl;
   pthread_exit(NULL);
}

int main () {
   pthread_t threads[NUM_THREADS];
   int rc;
   int i;
   
   for( i = 0; i < NUM_THREADS; i++ ) {
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], NULL, PrintHello, (void *)i);
      
      if (rc) {
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

使用 -lpthread 库编译以下程序,方法如下:

$gcc test.cpp -lpthread

现在,执行您的程序,它将给出以下输出:

main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Hello World! Thread ID, 0
Hello World! Thread ID, 1
Hello World! Thread ID, 2
Hello World! Thread ID, 3
Hello World! Thread ID, 4

向线程传递参数

此示例演示如何通过结构传递多个参数。您可以将任何数据类型传递给线程回调,因为它指向 void,如下例所示:

#include <iostream>
#include <cstdlib>
#include <pthread.h>

using namespace std;

#define NUM_THREADS 5

struct thread_data {
   int  thread_id;
   char *message;
};

void *PrintHello(void *threadarg) {
   struct thread_data *my_data;
   my_data = (struct thread_data *) threadarg;

   cout << "Thread ID : " << my_data->thread_id ;
   cout << " Message : " << my_data->message << endl;

   pthread_exit(NULL);
}

int main () {
   pthread_t threads[NUM_THREADS];
   struct thread_data td[NUM_THREADS];
   int rc;
   int i;

   for( i = 0; i < NUM_THREADS; i++ ) {
      cout <<"main() : creating thread, " << i << endl;
      td[i].thread_id = i;
      td[i].message = "This is message";
      rc = pthread_create(&threads[i], NULL, PrintHello, (void *)&td[i]);
      
      if (rc) {
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }
   pthread_exit(NULL);
}

编译并执行上述代码时,会产生以下结果:

main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Thread ID : 3 Message : This is message
Thread ID : 2 Message : This is message
Thread ID : 0 Message : This is message
Thread ID : 1 Message : This is message
Thread ID : 4 Message : This is message

连接和分离线程

我们可以使用以下两个例程来连接或分离线程:

pthread_join (threadid, status) 
pthread_detach (threadid) 

pthread_join() 子例程会阻塞调用线程,直到指定的 'threadid' 线程终止。创建线程时,其属性之一定义它是可连接的还是已分离的。只有创建为可连接的线程才能连接。如果线程创建为已分离的,则永远无法连接它。

此示例演示如何使用 Pthread join 例程等待线程完成。

#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>

using namespace std;

#define NUM_THREADS 5

void *wait(void *t) {
   int i;
   long tid;

   tid = (long)t;

   sleep(1);
   cout << "Sleeping in thread " << endl;
   cout << "Thread with id : " << tid << "  ...exiting " << endl;
   pthread_exit(NULL);
}

int main () {
   int rc;
   int i;
   pthread_t threads[NUM_THREADS];
   pthread_attr_t attr;
   void *status;

   // Initialize and set thread joinable
   pthread_attr_init(&attr);
   pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

   for( i = 0; i < NUM_THREADS; i++ ) {
      cout << "main() : creating thread, " << i << endl;
      rc = pthread_create(&threads[i], &attr, wait, (void *)i );

      
      if (rc) {
         cout << "Error:unable to create thread," << rc << endl;
         exit(-1);
      }
   }

   // free attribute and wait for the other threads
   pthread_attr_destroy(&attr);
   for( i = 0; i < NUM_THREADS; i++ ) {
      rc = pthread_join(threads[i], &status);
      if (rc) {
         cout << "Error:unable to join," << rc << endl;
         exit(-1);
      }
      
      cout << "Main: completed thread id :" << i ;
      cout << "  exiting with status :" << status << endl;
   }

   cout << "Main: program exiting." << endl;
   pthread_exit(NULL);
}

编译并执行上述代码时,会产生以下结果:

main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Sleeping in thread
Thread with id : 0 .... exiting
Sleeping in thread
Thread with id : 1 .... exiting
Sleeping in thread
Thread with id : 2 .... exiting
Sleeping in thread
Thread with id : 3 .... exiting
Sleeping in thread
Thread with id : 4 .... exiting
Main: completed thread id :0  exiting with status :0
Main: completed thread id :1  exiting with status :0
Main: completed thread id :2  exiting with status :0
Main: completed thread id :3  exiting with status :0
Main: completed thread id :4  exiting with status :0
Main: program exiting.

C++ Web编程

什么是 CGI?

  • 公共网关接口 (CGI) 是一组标准,定义了如何在 Web 服务器和自定义脚本之间交换信息。

  • CGI 规范目前由 NCSA 维护,NCSA 对 CGI 的定义如下:

  • 公共网关接口 (CGI) 是外部网关程序与信息服务器(例如 HTTP 服务器)交互的标准。

  • 当前版本为 CGI/1.1,CGI/1.2 正在开发中。

Web 浏览

为了理解 CGI 的概念,让我们看看当我们点击超链接浏览特定网页或 URL 时会发生什么。

  • 您的浏览器联系 HTTP Web 服务器并请求 URL,即文件名。

  • Web 服务器将解析 URL 并查找文件名。如果找到请求的文件,则 Web 服务器将该文件发送回浏览器,否则发送错误消息,指示您请求的文件错误。

  • Web 浏览器接收来自 Web 服务器的响应,并根据接收到的响应显示接收到的文件或错误消息。

但是,可以以这样一种方式设置 HTTP 服务器:每当请求某个目录中的文件时,该文件不会被发送回来;而是作为程序执行,程序生成的输出被发送回您的浏览器以显示。

公共网关接口 (CGI) 是一种标准协议,用于启用应用程序(称为 CGI 程序或 CGI 脚本)与 Web 服务器和客户端交互。这些 CGI 程序可以用 Python、PERL、Shell、C 或 C++ 等编写。

CGI 架构图

以下简单程序显示了 CGI 的简单架构:

CGI Architecture

Web 服务器配置

在继续进行 CGI 编程之前,请确保您的 Web 服务器支持 CGI 并已配置为处理 CGI 程序。所有要由 HTTP 服务器执行的 CGI 程序都保存在预配置的目录中。此目录称为 CGI 目录,按照惯例,其名称为 /var/www/cgi-bin。按照惯例,CGI 文件的扩展名为 .cgi,尽管它们是 C++ 可执行文件。

默认情况下,Apache Web 服务器配置为在 /var/www/cgi-bin 中运行 CGI 程序。如果要指定其他目录来运行 CGI 脚本,可以修改 httpd.conf 文件中的以下部分:

<Directory "/var/www/cgi-bin">
   AllowOverride None
   Options ExecCGI
   Order allow,deny
   Allow from all
</Directory>
 
<Directory "/var/www/cgi-bin">
   Options All
</Directory>

这里,我假设您的 Web 服务器已成功启动并运行,并且您可以运行其他 CGI 程序,例如 Perl 或 Shell 等。

第一个 CGI 程序

考虑以下 C++ 程序内容:

#include <iostream>
using namespace std;

int main () {
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>Hello World - First CGI Program</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
   cout << "<h2>Hello World! This is my first CGI program</h2>\n";
   cout << "</body>\n";
   cout << "</html>\n";
   
   return 0;
}

编译上述代码并将可执行文件命名为 cplusplus.cgi。此文件保存在 /var/www/cgi-bin 目录中,其内容如下。在运行 CGI 程序之前,请确保已使用 chmod 755 cplusplus.cgi UNIX 命令更改文件的模式以使其可执行。

我的第一个 CGI 程序

上述 C++ 程序是一个简单的程序,它将其输出写入 STDOUT 文件,即屏幕。还有一个重要的附加功能,即第一行打印 Content-type:text/html\r\n\r\n。此行被发送回浏览器,并指定要在浏览器屏幕上显示的内容类型。现在您必须已经理解了 CGI 的基本概念,并且可以使用 Python 编写许多复杂的 CGI 程序。C++ CGI 程序可以与任何其他外部系统(例如 RDBMS)交互以交换信息。

HTTP 头

Content-type:text/html\r\n\r\n 行是 HTTP 头的一部分,它被发送到浏览器以理解内容。所有 HTTP 头都将采用以下形式:

HTTP Field Name: Field Content
 
For Example
Content-type: text/html\r\n\r\n

还有一些其他重要的 HTTP 头,您将在 CGI 编程中经常使用。

序号 头及描述
1

Content-type

定义返回文件的格式的 MIME 字符串。例如 Content-type:text/html。

2

Expires: Date

信息失效的日期。浏览器应使用此日期来决定何时需要刷新页面。有效的日期字符串应采用 01 Jan 1998 12:00:00 GMT 格式。

3

Location: URL

应返回的 URL,而不是请求的 URL。您可以使用此字段将请求重定向到任何文件。

4

Last-modified: Date

资源上次修改的日期。

5

Content-length: N

返回数据的长度(以字节为单位)。浏览器使用此值来报告文件的估计下载时间。

6

Set-Cookie: String

设置通过 string 传递的 Cookie。

CGI 环境变量

所有 CGI 程序都可以访问以下环境变量。这些变量在编写任何 CGI 程序时都起着重要的作用。

序号 变量名称及描述
1

CONTENT_TYPE

内容的数据类型,在客户端向服务器发送附加内容时使用。例如文件上传等。

2

CONTENT_LENGTH

只有 POST 请求才可用的查询信息长度。

3

HTTP_COOKIE

以键值对的形式返回设置的 Cookie。

4

HTTP_USER_AGENT

User-Agent 请求头字段包含有关发起请求的用户代理的信息。它是 Web 浏览器的名称。

5

PATH_INFO

CGI 脚本的路径。

6

QUERY_STRING

使用 GET 方法请求发送的 URL 编码信息。

7

REMOTE_ADDR

发出请求的远程主机的 IP 地址。这对于日志记录或身份验证非常有用。

8

REMOTE_HOST

发出请求的主机的完全限定名称。如果此信息不可用,则可以使用 REMOTE_ADDR 获取 IR 地址。

9

REQUEST_METHOD

用于发出请求的方法。最常用的方法是 GET 和 POST。

10

SCRIPT_FILENAME

CGI 脚本的完整路径。

11

SCRIPT_NAME

CGI 脚本的名称。

12

SERVER_NAME

服务器的主机名或 IP 地址。

13

SERVER_SOFTWARE

服务器正在运行的软件的名称和版本。

这是一个小的 CGI 程序,用于列出所有 CGI 变量。

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

const string ENV[ 24 ] = {
   "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE",   
   "HTTP_ACCEPT", "HTTP_ACCEPT_ENCODING",             
   "HTTP_ACCEPT_LANGUAGE", "HTTP_CONNECTION",         
   "HTTP_HOST", "HTTP_USER_AGENT", "PATH",            
   "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT",      
   "REQUEST_METHOD", "REQUEST_URI", "SCRIPT_FILENAME",
   "SCRIPT_NAME", "SERVER_ADDR", "SERVER_ADMIN",      
   "SERVER_NAME","SERVER_PORT","SERVER_PROTOCOL",     
   "SERVER_SIGNATURE","SERVER_SOFTWARE" };   

int main () {
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>CGI Environment Variables</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
   cout << "<table border = \"0\" cellspacing = \"2\">";

   for ( int i = 0; i < 24; i++ ) {
      cout << "<tr><td>" << ENV[ i ] << "</td><td>";
      
      // attempt to retrieve value of environment variable
      char *value = getenv( ENV[ i ].c_str() );  
      if ( value != 0 ) {
         cout << value;                                 
      } else {
         cout << "Environment variable does not exist.";
      }
      cout << "</td></tr>\n";
   }
   
   cout << "</table><\n";
   cout << "</body>\n";
   cout << "</html>\n";
   
   return 0;
}

C++ CGI 库

对于实际示例,您需要通过 CGI 程序执行许多操作。有一个为 C++ 程序编写的 CGI 库,您可以从 ftp://ftp.gnu.org/gnu/cgicc/ 下载,并按照步骤安装该库:

$tar xzf cgicc-X.X.X.tar.gz 
$cd cgicc-X.X.X/ 
$./configure --prefix=/usr 
$make
$make install

您可以查看在 ‘C++ CGI 库文档’ 中提供的相关文档。

GET 和 POST 方法

您肯定遇到过许多需要将某些信息从浏览器传递到 Web 服务器,最终传递到 CGI 程序的情况。浏览器最常使用两种方法将此信息传递到 Web 服务器。这些方法是 GET 方法和 POST 方法。

使用 GET 方法传递信息

GET 方法发送附加到页面请求的编码用户信息。页面和编码信息由 ? 字符分隔,如下所示:

http://www.test.com/cgi-bin/cpp.cgi?key1=value1&key2=value2

GET 方法是将信息从浏览器传递到 Web 服务器的默认方法,它会生成一个长字符串,该字符串显示在浏览器的 Location: 框中。如果要将密码或其他敏感信息传递到服务器,切勿使用 GET 方法。GET 方法有大小限制,您最多可以在请求字符串中传递 1024 个字符。

使用 GET 方法时,信息使用 QUERY_STRING http 头传递,并且可以通过 QUERY_STRING 环境变量在 CGI 程序中访问。

您可以通过简单地连接键值对以及任何URL来传递信息,也可以使用HTML <FORM>标签使用GET方法传递信息。

简单的URL示例:GET方法

这是一个简单的URL,它将使用GET方法将两个值传递给hello_get.py程序。

/cgi-bin/cpp_get.cgi?first_name=ZARA&last_name=ALI

下面是一个程序,用于生成cpp_get.cgi CGI程序来处理Web浏览器提供的输入。我们将使用C++ CGI库,这使得访问传递的信息非常容易。

#include <iostream>
#include <vector>  
#include <string>  
#include <stdio.h>  
#include <stdlib.h> 

#include <cgicc/CgiDefs.h> 
#include <cgicc/Cgicc.h> 
#include <cgicc/HTTPHTMLHeader.h> 
#include <cgicc/HTMLClasses.h>  

using namespace std;
using namespace cgicc;

int main () {
   Cgicc formData;
   
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>Using GET and POST Methods</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";

   form_iterator fi = formData.getElement("first_name");  
   if( !fi->isEmpty() && fi != (*formData).end()) {  
      cout << "First name: " << **fi << endl;  
   } else {
      cout << "No text entered for first name" << endl;  
   }
   
   cout << "<br/>\n";
   fi = formData.getElement("last_name");  
   if( !fi->isEmpty() &&fi != (*formData).end()) {  
      cout << "Last name: " << **fi << endl;  
   } else {
      cout << "No text entered for last name" << endl;  
   }
   
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
   
   return 0;
}

现在,按如下方式编译上述程序:

$g++ -o cpp_get.cgi cpp_get.cpp -lcgicc

生成cpp_get.cgi并将其放入您的CGI目录中,然后尝试使用以下链接访问:

/cgi-bin/cpp_get.cgi?first_name=ZARA&last_name=ALI

这将生成以下结果:

First name: ZARA 
Last name: ALI 

简单的表单示例:GET方法

这是一个简单的示例,它使用HTML表单和提交按钮传递两个值。我们将使用相同的CGI脚本cpp_get.cgi来处理此输入。

<form action = "/cgi-bin/cpp_get.cgi" method = "get">
   First Name: <input type = "text" name = "first_name">  <br />
 
   Last Name: <input type = "text" name = "last_name" />
   <input type = "submit" value = "Submit" />
</form>

这是上面表单的实际输出。您输入名字和姓氏,然后单击提交按钮查看结果。

名字姓氏

使用POST方法传递信息

将信息传递给CGI程序的一种通常更可靠的方法是POST方法。此方法以与GET方法完全相同的方式打包信息,但不是将其作为文本字符串发送到URL中的?之后,而是将其作为单独的消息发送。此消息以标准输入的形式进入CGI脚本。

相同的cpp_get.cgi程序也将处理POST方法。让我们采用与上述相同的示例,它使用HTML表单和提交按钮传递两个值,但这次使用POST方法,如下所示:

<form action = "/cgi-bin/cpp_get.cgi" method = "post">
   First Name: <input type = "text" name = "first_name"><br />
   Last Name: <input type = "text" name = "last_name" />
 
   <input type = "submit" value = "Submit" />
</form>

这是上面表单的实际输出。您输入名字和姓氏,然后单击提交按钮查看结果。

名字姓氏

将复选框数据传递给CGI程序

当需要选择多个选项时,使用复选框。

这是一个带有两个复选框的表单的HTML代码示例:

<form action = "/cgi-bin/cpp_checkbox.cgi" method = "POST" target = "_blank">
   <input type = "checkbox" name = "maths" value = "on" /> Maths
   <input type = "checkbox" name = "physics" value = "on" /> Physics
   <input type = "submit" value = "Select Subject" />
</form>

此代码的结果是以下表单:

数学物理

下面是C++程序,它将生成cpp_checkbox.cgi脚本以处理Web浏览器通过复选框按钮提供的输入。

#include <iostream>
#include <vector>  
#include <string>  
#include <stdio.h>  
#include <stdlib.h> 

#include <cgicc/CgiDefs.h> 
#include <cgicc/Cgicc.h> 
#include <cgicc/HTTPHTMLHeader.h> 
#include <cgicc/HTMLClasses.h> 

using namespace std;
using namespace cgicc;

int main () {
   Cgicc formData;
   bool maths_flag, physics_flag;

   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>Checkbox Data to CGI</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";

   maths_flag = formData.queryCheckbox("maths");
   if( maths_flag ) {  
      cout << "Maths Flag: ON " << endl;  
   } else {
      cout << "Maths Flag: OFF " << endl;  
   }
   cout << "<br/>\n";

   physics_flag = formData.queryCheckbox("physics");
   if( physics_flag ) {  
      cout << "Physics Flag: ON " << endl;  
   } else {
      cout << "Physics Flag: OFF " << endl;  
   }
   
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
   
   return 0;
}

将单选按钮数据传递给CGI程序

单选按钮仅用于选择一个选项。

这是一个带有两个单选按钮的表单的HTML代码示例:

<form action = "/cgi-bin/cpp_radiobutton.cgi" method = "post" target = "_blank">
   <input type = "radio" name = "subject" value = "maths" checked = "checked"/> Maths 
   <input type = "radio" name = "subject" value = "physics" /> Physics
   <input type = "submit" value = "Select Subject" />
</form>

此代码的结果是以下表单:

数学物理

下面是C++程序,它将生成cpp_radiobutton.cgi脚本以处理Web浏览器通过单选按钮提供的输入。

#include <iostream>
#include <vector>  
#include <string>  
#include <stdio.h>  
#include <stdlib.h> 

#include <cgicc/CgiDefs.h> 
#include <cgicc/Cgicc.h> 
#include <cgicc/HTTPHTMLHeader.h> 
#include <cgicc/HTMLClasses.h> 

using namespace std;
using namespace cgicc;

int main () {
   Cgicc formData;
  
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>Radio Button Data to CGI</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";

   form_iterator fi = formData.getElement("subject");  
   if( !fi->isEmpty() && fi != (*formData).end()) {  
      cout << "Radio box selected: " << **fi << endl;  
   }
  
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
   
   return 0;
}

将文本区域数据传递给CGI程序

当必须将多行文本传递给CGI程序时,使用TEXTAREA元素。

这是一个带有文本区域框的表单的HTML代码示例:

<form action = "/cgi-bin/cpp_textarea.cgi" method = "post" target = "_blank">
   <textarea name = "textcontent" cols = "40" rows = "4">
      Type your text here...
   </textarea>
   <input type = "submit" value = "Submit" />
</form>

此代码的结果是以下表单:

下面是C++程序,它将生成cpp_textarea.cgi脚本以处理Web浏览器通过文本区域提供的输入。

#include <iostream>
#include <vector>  
#include <string>  
#include <stdio.h>  
#include <stdlib.h> 

#include <cgicc/CgiDefs.h> 
#include <cgicc/Cgicc.h> 
#include <cgicc/HTTPHTMLHeader.h> 
#include <cgicc/HTMLClasses.h> 

using namespace std;
using namespace cgicc;

int main () {
   Cgicc formData;
  
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>Text Area Data to CGI</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";

   form_iterator fi = formData.getElement("textcontent");  
   if( !fi->isEmpty() && fi != (*formData).end()) {  
      cout << "Text Content: " << **fi << endl;  
   } else {
      cout << "No text entered" << endl;  
   }
  
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
   
   return 0;
}

将下拉框数据传递给CGI程序

当有很多选项可用,但只选择一两个时,使用下拉框。

这是一个带有下拉框的表单的HTML代码示例:

<form action = "/cgi-bin/cpp_dropdown.cgi" method = "post" target = "_blank">
   <select name = "dropdown">
      <option value = "Maths" selected>Maths</option>
      <option value = "Physics">Physics</option>
   </select>
   
   <input type = "submit" value = "Submit"/>
</form>

此代码的结果是以下表单:

下面是C++程序,它将生成cpp_dropdown.cgi脚本以处理Web浏览器通过下拉框提供的输入。

#include <iostream>
#include <vector>  
#include <string>  
#include <stdio.h>  
#include <stdlib.h> 

#include <cgicc/CgiDefs.h> 
#include <cgicc/Cgicc.h> 
#include <cgicc/HTTPHTMLHeader.h> 
#include <cgicc/HTMLClasses.h> 

using namespace std;
using namespace cgicc;

int main () {
   Cgicc formData;
  
   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>Drop Down Box Data to CGI</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";

   form_iterator fi = formData.getElement("dropdown");  
   if( !fi->isEmpty() && fi != (*formData).end()) {  
      cout << "Value Selected: " << **fi << endl;  
   }
  
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
   
   return 0;
}

在CGI中使用Cookie

HTTP协议是一种无状态协议。但是对于商业网站来说,需要在不同的页面之间维护会话信息。例如,一个用户注册在完成许多页面后结束。但是如何跨所有网页维护用户的会话信息呢?

在许多情况下,使用cookie是记住和跟踪偏好、购买、佣金以及其他改善访问者体验或网站统计信息所需信息的最高效方法。

工作原理

您的服务器以cookie的形式向访问者的浏览器发送一些数据。浏览器可能会接受cookie。如果接受,则将其作为纯文本记录存储在访问者的硬盘驱动器上。现在,当访问者到达您网站上的另一个页面时,cookie即可供检索。检索后,您的服务器就知道/记得存储了什么。

cookie是5个变长字段的纯文本数据记录:

  • 过期时间 - 这显示cookie将过期的日期。如果为空,则cookie将在访问者退出浏览器时过期。

  • - 这显示您网站的域名。

  • 路径 - 这显示设置cookie的目录或网页的路径。如果您想从任何目录或页面检索cookie,这可以为空。

  • 安全 - 如果此字段包含单词“安全”,则cookie只能使用安全服务器检索。如果此字段为空,则不存在此类限制。

  • 名称=值 - cookie以键值对的形式设置和检索。

设置Cookie

向浏览器发送cookie非常容易。这些cookie将在“Content-type”字段之前的HTTP标头中发送。假设您想将UserID和Password设置为cookie。因此,cookie设置将按如下方式完成

#include <iostream>
using namespace std;

int main () {
   cout << "Set-Cookie:UserID = XYZ;\r\n";
   cout << "Set-Cookie:Password = XYZ123;\r\n";
   cout << "Set-Cookie:Domain = www.tutorialspoint.com;\r\n";
   cout << "Set-Cookie:Path = /perl;\n";
   cout << "Content-type:text/html\r\n\r\n";

   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>Cookies in CGI</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";

   cout << "Setting cookies" << endl;  
  
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
   
   return 0;
}

通过此示例,您必须了解如何设置cookie。我们使用Set-Cookie HTTP标头来设置cookie。

在这里,可以选择设置cookie属性,如过期时间、域和路径。值得注意的是,cookie是在发送神奇行“Content-type:text/html\r\n\r\n”之前设置的。

编译上述程序以生成setcookies.cgi,并尝试使用以下链接设置cookie。它将在您的计算机上设置四个cookie:

/cgi-bin/setcookies.cgi

检索Cookie

检索所有已设置的cookie很容易。cookie存储在CGI环境变量HTTP_COOKIE中,它们将具有以下形式。

key1 = value1; key2 = value2; key3 = value3....

这是一个检索cookie的示例。

#include <iostream>
#include <vector>  
#include <string>  
#include <stdio.h>  
#include <stdlib.h> 

#include <cgicc/CgiDefs.h> 
#include <cgicc/Cgicc.h> 
#include <cgicc/HTTPHTMLHeader.h> 
#include <cgicc/HTMLClasses.h>

using namespace std;
using namespace cgicc;

int main () {
   Cgicc cgi;
   const_cookie_iterator cci;

   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>Cookies in CGI</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";
   cout << "<table border = \"0\" cellspacing = \"2\">";
   
   // get environment variables
   const CgiEnvironment& env = cgi.getEnvironment();

   for( cci = env.getCookieList().begin();
   cci != env.getCookieList().end(); 
   ++cci ) {
      cout << "<tr><td>" << cci->getName() << "</td><td>";
      cout << cci->getValue();                                 
      cout << "</td></tr>\n";
   }
   
   cout << "</table><\n";
   cout << "<br/>\n";
   cout << "</body>\n";
   cout << "</html>\n";
   
   return 0;
}

现在,编译上述程序以生成getcookies.cgi,并尝试获取计算机上所有可用cookie的列表:

/cgi-bin/getcookies.cgi

这将生成上一节中设置的全部四个cookie以及您计算机上设置的所有其他cookie的列表:

UserID XYZ 
Password XYZ123 
Domain www.tutorialspoint.com 
Path /perl 

文件上传示例

要上传文件,HTML表单必须将enctype属性设置为multipart/form-data。带有文件类型的input标签将创建一个“浏览”按钮。

<html>
   <body>
      <form enctype = "multipart/form-data" action = "/cgi-bin/cpp_uploadfile.cgi"
         method = "post">
         <p>File: <input type = "file" name = "userfile" /></p>
         <p><input type = "submit" value = "Upload" /></p>
      </form>
   </body>
</html>

此代码的结果是以下表单:

文件

注意 - 上述示例已被故意禁用,以阻止人们将文件上传到我们的服务器。但是您可以尝试使用您的服务器上的上述代码。

这是处理文件上传的脚本cpp_uploadfile.cpp

#include <iostream>
#include <vector>  
#include <string>  
#include <stdio.h>  
#include <stdlib.h> 

#include <cgicc/CgiDefs.h> 
#include <cgicc/Cgicc.h> 
#include <cgicc/HTTPHTMLHeader.h> 
#include <cgicc/HTMLClasses.h>

using namespace std;
using namespace cgicc;

int main () {
   Cgicc cgi;

   cout << "Content-type:text/html\r\n\r\n";
   cout << "<html>\n";
   cout << "<head>\n";
   cout << "<title>File Upload in CGI</title>\n";
   cout << "</head>\n";
   cout << "<body>\n";

   // get list of files to be uploaded
   const_file_iterator file = cgi.getFile("userfile");
   if(file != cgi.getFiles().end()) {
      // send data type at cout.
      cout << HTTPContentHeader(file->getDataType());
      // write content at cout.
      file->writeToStream(cout);
   }
   cout << "<File uploaded successfully>\n";
   cout << "</body>\n";
   cout << "</html>\n";
   
   return 0;
}

上面的示例用于在cout流中写入内容,但是您可以打开文件流并将上传文件的內容保存到所需位置的文件中。

希望您喜欢本教程。如果喜欢,请给我们发送您的反馈。

广告
© . All rights reserved.