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 系统可以使用 Notepad,而 Windows、Linux 或 UNIX 系统则可以使用 vim 或 vim。
使用编辑器创建的文件称为源文件,对于 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++ 程序时,它可以被定义为一组通过调用彼此的方法进行通信的对象。现在让我们简要了解一下类、对象、方法和实例变量的含义。
-
对象 − 对象具有状态和行为。例如:狗既有状态(颜色、名称、品种),也有行为(摇尾巴、吠叫、进食)。对象是类的一个实例。
-
类 − 类可以定义为一个模板/蓝图,用于描述其类型对象支持的行为或状态。
-
方法 − 方法本质上是一种行为。一个类可以包含许多方法。方法中编写逻辑、操作数据并执行所有操作。
-
实例变量 − 每个对象都有其独特的实例变量集。对象的状态由赋给这些实例变量的值创建。
C++ 程序结构
C++ 程序的基本结构由以下部分组成:
- 头文件包含部分:在此部分中,我们将包含程序中将要使用的所有必需头文件及其函数。
- namespace 命名空间部分:在此部分中,我们将使用命名空间。
- main() 部分:在此部分中,我们编写主代码。 main() 函数是任何 C++ 编程代码的入口点,程序从这里开始执行。
要了解更多信息,请阅读:C++ Hello, World 程序。
示例
让我们看一段打印 Hello World 的简单代码。
#include <iostream> using namespace std; // main() 是程序执行的起点。 int main() { cout << "Hello World"; // 打印 Hello World return 0; }
示例说明
让我们看一下上述程序的各个部分 -
C++ 语言定义了几个头文件,其中包含对程序必要或有用的信息。对于此程序,需要头文件 <iostream>。
行 using namespace std; 告诉编译器使用 std 命名空间。命名空间是 C++ 中相对较新的功能。
下一行"// main() 是程序执行的开始位置。"是 C++ 中可用的单行注释。单行注释以 // 开头,并在行尾结束。
int main() 行是程序开始执行的主函数。
下一行 cout << "Hello World"; 使消息"Hello World"显示在屏幕上。
下一行 return 0; 终止 main() 函数,并使其将值 0 返回给调用进程。
编译和执行 C++ 程序
让我们看看如何保存文件、编译和运行程序。请按照以下步骤操作:
打开文本编辑器并添加上述代码。
将文件另存为:hello.cpp
打开命令提示符并转到保存文件的目录。
输入"g++ hello.cpp"并按回车键编译代码。如果代码中没有错误,命令提示符将跳转到下一行并生成一个.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++ 是一种区分大小写的编程语言。因此,Manpower 和 manpower 在 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; // 获取水果总数
在上述语句 2 中,fruit 和 = 之间以及 = 和 apples 之间不需要空格,但如果您希望提高可读性,可以添加一些空格。
C++ 中的注释
C++ 注释
程序注释是可以包含在 C++ 代码中的解释性语句。这些注释有助于任何阅读源代码的人。所有编程语言都允许某种形式的注释。
C++ 注释的类型
C++ 支持两种类型的注释:单行注释和多行注释。
下一节将详细解释 C++ 注释的类型:
1. C++ 单行注释
单行注释以 // 开头,一直到行尾。这些注释只能持续到行尾,下一行会引出新的注释。
语法
以下语法展示了如何在 C++ 中使用单行注释:
// 待注释的文本
示例
在以下示例中,我们将创建单行注释 -
#include <iostream> using namespace std; int main() { // 这是单行注释 cout << "Hello world!" << endl; // 对于新行,我们必须使用新的注释部分 cout << "This is second line."; return 0; }
输出
Hello world! 这是第二行。
2. C++ 多行注释
多行注释以 /* 开头,以 */ 结尾。这些符号之间的任何文本都仅被视为注释。
语法
以下语法展示了如何在 C++ 中使用多行注释:
/* 这是一个注释 */ /* C++ 注释也可以 跨越多行 */
C++ 数据类型
使用任何语言编写程序时,您都需要使用各种变量来存储各种信息。变量只不过是用于存储值的保留内存位置。这意味着当您创建变量时,您会在内存中预留一些空间。
您可能希望存储各种数据类型的信息,例如字符、宽字符、整数、浮点数、双精度浮点数、布尔值等。操作系统会根据变量的数据类型分配内存,并决定哪些数据可以存储在保留内存中。
原始内置类型
C++ 为程序员提供了丰富的内置数据类型以及用户定义的数据类型。下表列出了七种基本的 C++ 数据类型 -
可以使用一个或多个以下类型修饰符来修改几种基本类型 -
- 有符号
- 无符号
- 短整型
- 长整型
下表显示了变量类型、存储值所需的内存大小以及此类变量可存储的最大值和最小值。
类型 | 典型位宽 | 典型范围 |
---|---|---|
char | 1byte | -127 to 127 or 0 to 255 |
unsigned char | 1byte | 0 to 255 |
signed char | 1byte | -127 to 127 |
int | 4bytes | -2147483648 to 2147483647 |
unsigned int | 4bytes | 0 to 4294967295 |
signed int | 4bytes | -2147483648 to 2147483647 |
short int | 2bytes | -32768 to 32767 |
unsigned short int | 2bytes | 0 to 65,535 |
signed short int | 2bytes | -32768 to 32767 |
long int | 8bytes | -9223372036854775808 to 9223372036854775807 |
signed long int | 8bytes | same as long int |
unsigned long int | 8bytes | 0 to 18446744073709551615 |
long long int | 8bytes | -(2^63) to (2^63)-1 |
unsigned long long int | 8bytes | 0 to 18,446,744,073,709,551,615 |
float | 4bytes | |
double | 8bytes | |
long double | 12bytes | |
wchar_t | 2 or 4 bytes | 1 wide character |
变量的大小可能与上表所示不同,具体取决于您使用的编译器和计算机。
示例
以下示例将在您的计算机上生成各种数据类型的正确大小。
#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
示例
以下是另一个示例:
#include <iostream> #include <limits> using namespace std; int main() { std::cout << "Int Min " << std::numeric_limits<int>::min() << endl; std::cout << "Int Max " << std::numeric_limits<int>::max() << endl; std::cout << "Unsigned Int Min " << std::numeric_limits<unsigned int>::min() << endl; std::cout << "Unsigned Int Max " << std::numeric_limits<unsigned int>::max() << endl; std::cout << "Long Int Min " << std::numeric_limits<long int>::min() << endl; std::cout << "Long Int Max " << std::numeric_limits<long int>::max() << endl; std::cout << "Unsigned Long Int Min " << std::numeric_limits<unsigned long int>::min() <<endl; std::cout << "Unsigned Long Int Max " << std::numeric_limits<unsigned long int>::max() << endl; }
派生数据类型
在 C++ 中,从预定义数据类型派生而来的数据类型称为派生数据类型。这些数据类型可分为四类,即:
1. 函数
函数是用户定义数据类型最简单的形式。它包含返回类型、函数名称和输入参数。
语法
return_type function_name(input_param1, input_param2){ <function_body> }
示例
#include <iostream> using namespace std; string func(int n){ //返回 n 是奇数还是偶数 if(n%2) return "Given number is Odd !"; else return "Given number is Even !"; } int main(){ int a; //输入一个数字 cin>>a; cout<<func(a); //一个简单的函数,用于检查 //数字是奇数还是偶数 return 0; }
输出
Given number is Even !
2. 数组
数组由一系列相同数据类型的元素组成。数组元素存储在存储空间中连续的内存位置。
语法
data_type array_name[array_size];
示例
#include <iostream> using namespace std; int main(){ int arr[5]={1,2,3,2,1}; //定义一个大小为 5 的整数数组 for(auto it:arr) cout<<it<<" "; //打印数组元素 return 0; }
输出
1 2 3 2 1
3. 指针
指针是对先前定义元素的引用。指针的值返回与其关联元素的地址位置。
语法
data_type * pointer_name=& variable_name;
示例
#include <iostream> using namespace std; int main() { int a=20; //声明变量a int *p= &a; //将指针赋值给a cout<<"Address of variable a: "<<p<<endl; cout<<"Value of variable a: "<<*p<<endl; return 0; }
输出
Address of variable a: 0x7ffc49a8637c Value of variable a: 20
4. 引用
引用变量用于创建具有相同引用的变量副本。因此,对引用变量的更改也会反映在原始变量上。
语法
data_type & reference_name= variable_name;
示例
#include <iostream> using namespace std; int main(){ int c=11; int& refer=c; cout<<"Initially value of integer is: "<<c<<endl; refer=121; cout<<"After changing value using refer variable :"<<c<<endl; return 0; }
输出
Initially value of integer is: 11 After changing value using refer variable :121
用户定义数据类型
用户自定义数据类型是指用户直观定义,无需使用任何预定义数据类型的数据类型。这些数据类型可以进一步分为五种类型,即:
1. 类
类是面向对象编程中定义的一种自定义数据类型,用于构造对象。它是对象的框架,可以包含构造函数、方法以及多态、继承等面向对象编程 (OOP) 概念。
语法
class Class_name{ <class body> class_name(parameters) { <constructor body> } return_type method_name(paremeters){ <method body> } }
示例
#include <iostream> using namespace std; class TP{ public: string tp; void print(){ cout<<tp<<endl; } }; int main(){ TP object; object.tp="I Love Tutorialspoint !!!"; object.print(); return 0; }
输出
I Love Tutorialspoint !!!
2. 结构体 (struct)
在结构体数据类型中,用户可以在结构体主体内引入多个原始数据类型。
语法
struct struct_name{ data_type1 var_name1; data_type2 var_name2; }
示例
#include <iostream> using namespace std; struct TP{ string tp; int grade; }; int main(){ TP object; object.tp="I Love Tutorialspoint !!!"; object.grade=10; cout<<object.tp<<endl; cout<<"How much would you rate it?"<<" : "<< object.grade; return 0; }
输出
I Love Tutorialspoint !!! How much would you rate it? : 10
3. 联合
联合类似于结构体。在这种情况下,所有变量的内存位置相同,并且所有变量共享同一个引用。因此,一个值的更改会导致所有其他值也随之更改。
语法
union union_name{ data_type var_name1; data_type var_name2; };
示例
#include <iostream> using namespace std; union TP{ int tp1,tp2; }; int main(){ union TP t; t.tp1=2; cout<<"Value of tp1 initially: "<<t.tp1<<endl; t.tp2=4; cout<<"When we change tp2, value of tp1 is : "<<t.tp1<<endl; return 0; }
输出
Value of tp1 initially: 2 When we change tp2, value of tp1 is : 4
4. 枚举(Enum)
枚举(或简称 enum)是一种用户定义的数据类型,用于在程序中为整型常量命名。这可以提高程序的可读性。
语法
enum enum_name{ var_name1 , var_name2, }
示例
#include <iostream> using namespace std; enum TP{ C, Java, Python, Ruby, Kotlin, Javascript, TypeScript, Others}; int main(){ enum TP course; cout<<"Which course do you love the most?"<<endl; course=Kotlin; cout<<"I love the "<<course+1<<"th course !!!"; return 0; }
输出
Which course do you love the most? I love the 5th course !!!
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++ 变量命名
变量名称可以由字母、数字和下划线组成。它必须以字母或下划线开头。由于 C++ 区分大小写,因此大小写字母有区别。
C++ 中变量命名约定的其他一些规则 -
- 关键字不能用作变量名。
- 变量名中不能包含空格。
- 变量名中不能使用连字符 (-)。
- 变量名不能以特殊字符和数字开头。它应该是大写字母、小写字母或下划线 (_)。
有效变量名示例
一些有效的变量名如下 -
int age; int _age; int student_age; int studentAge;
无效变量名示例
一些无效变量名如下:-
int 2ndvariable; int student-age; int float; int student age; int #age;
C++ 变量的类型
如上一章所述,C++ 中有以下基本变量类型 -
Sr.No | 类型 &描述 |
---|---|
1 |
存储值 true 或 false。 |
2 |
通常为单个八位字节(一个字节)。这是一个整数类型。 |
3 | int 机器最自然的整数大小。 |
4 | float 单精度浮点值。 |
5 | double 双精度浮点值。 |
6 | void 表示不存在类型。 |
7 | wchar_t 宽字符类型。 |
C++ 还允许定义各种其他类型的变量,我们将在后续章节中介绍,例如枚举、指针、数组、引用、数据结构和类。
下一节将介绍如何定义、声明和使用各种类型的变量。
C++ 中的变量定义
变量定义告诉编译器在何处以及为变量创建多少存储空间。变量定义指定一种数据类型,并包含一个或多个该类型的变量列表,如下所示:-
语法
type 变量列表;
其中,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 类型变量。
C++ 中的变量初始化
变量可以在声明中初始化(赋予初始值)。初始化函数由一个等号后跟一个常量表达式组成,如下所示:-
语法
type variable_name = value;
示例
以下是一些示例:-
extern int d = 3, f = 5; // 声明 d 和 f。 int d = 3, f = 5; // 定义并初始化 d 和 f。 byte z = 22; // 定义并初始化 z。 char x = 'x'; // 变量 x 的值为 'x'。
对于没有初始化器的定义:具有静态存储期的变量隐式初始化为 NULL(所有字节的值均为 0);所有其他变量的初始值均未定义。
C++ 中的变量声明
变量声明向编译器保证存在一个具有给定类型和名称的变量,以便编译器无需了解变量的完整信息即可继续进行进一步编译。变量声明仅在编译时有意义,编译器在程序链接时需要实际的变量定义。
当您使用多个文件,并且在链接程序时可用的其中一个文件中定义变量时,变量声明非常有用。您将使用 extern 关键字在任何地方声明变量。虽然您可以在 C++ 程序中多次声明一个变量,但它只能在文件、函数或代码块中定义一次。
示例
尝试以下示例,其中一个变量在顶部声明,但在主函数内部定义 -
#include <iostream> using namespace std; // 变量声明: extern int a, b; extern int c; extern float f; int main () { // 变量定义: int a, b; int c; float f; // 实际初始化 a = 10; b = 20; c = a + b; cout << c << endl ; f = 70.0/3.0; cout << f << endl ; return 0; }
当编译并执行上述代码时,它会产生以下结果 -
30 23.3333
同样的概念也适用于函数声明,即在声明时提供函数名称,而其实际定义可以在其他任何地方给出。例如:-
// 函数声明 int func(); int main() { // 函数调用 int i = func(); } // 函数定义 int func() { return 0; }
左值和右值
C++ 中有两种表达式 -
左值 - 引用内存位置的表达式称为"左值"表达式。左值可以出现在赋值语句的左侧或右侧。
右值 - 术语"右值"是指存储在内存中某个地址的数据值。右值是一种不能被赋值的表达式,这意味着右值可以出现在赋值语句的右侧,但不能出现在赋值语句的左侧。
变量是左值,因此可以出现在赋值语句的左侧。数值字面量是右值,因此不能被赋值,也不能出现在赋值语句的左侧。以下是有效的语句 -
int g = 20;
但以下语句无效,会产生编译时错误 -
10 = 20;
C++ 中的变量作用域
作用域是程序的一个区域,广义上讲,变量可以在三个地方声明:
在函数或代码块内部,称为局部变量;
在函数参数的定义中,称为形式参数;
在所有函数之外,称为全局变量;
C++ 变量的作用域主要分为两类:
- 局部变量
- 全局变量
我们将在后续章节中学习什么是函数及其参数。这里我们来解释一下什么是局部变量和全局变量。

局部变量
变量 在函数或代码块内声明的变量是局部变量。它们只能由该函数或代码块内的语句使用。局部变量不被其自身以外的函数识别。
示例
以下是使用局部变量的示例 -
#include <iostream> using namespace std; int main () { // 局部变量声明 int a, b; int c; // 实际初始化 a = 10; b = 20; c = a + b; cout << c; return 0; }
全局变量
全局变量在所有函数之外定义,通常在程序顶部。全局变量的值会在程序的整个生命周期内保持不变。
全局变量可以被任何函数访问。也就是说,全局变量在声明后,在整个程序中都可以使用。
示例
以下是使用全局变量和局部变量的示例 -
#include <iostream> using namespace std; // 全局变量声明 int g; int main () { // 局部变量声明 int a, b; // 实际初始化 a = 10; b = 20; g = a + b; cout << g; return 0; }
局部变量和全局变量同名
程序中的局部变量和全局变量可以同名,但函数内部局部变量的值优先。
示例
#include <iostream> using namespace std; // 全局变量声明 int g = 20; int main () { // 局部变量声明 int g = 10; cout << g; return 0; }
当编译并执行上述代码时,它会产生以下结果 -
10
访问全局变量
当存在同名局部变量时,可以通过在变量名称前使用 SRO(作用域解析运算符):: 来访问全局变量。
示例
在下面的示例中,我们有同名的全局变量和局部变量,并访问和打印全局变量的值 -
#include <iostream> using namespace std; // 全局变量声明: int g = 20; int main() { // 局部变量声明: int g = 10; cout << "Value of g (Local variable): " << g; cout << endl; cout << "Value of g (Global variable): " << ::g; return 0; }
当编译并执行上述代码时,它会产生以下结果 -
Value of g (Local variable): 10 Value of g (Global variable): 20
初始化局部变量和全局变量
定义局部变量时,系统不会初始化它,您必须自行初始化。全局变量在您定义时会由系统自动初始化,如下所示:
数据类型 | 初始化器 |
---|---|
int | 0 |
char | '\0' |
float | 0 |
double | 0 |
pointer | NULL |
正确地初始化变量是一种很好的编程习惯,否则有时程序会产生意想不到的结果。
C++ 常量/字面量
常量是指程序无法更改的固定值,它们被称为字面量。
常量可以是任何基本数据类型,可分为整型、浮点型、字符型、字符串型和布尔型。
同样,常量与常规变量类似,不同之处在于它们的值在定义后无法修改。
整型字面量
整型字面量可以是十进制、八进制或十六进制常量。前缀指定基数或基数:0x 或 0X 表示十六进制,0 表示八进制,无表示十进制。
整数字面量还可以带有后缀,该后缀由 U 和 L 组成,分别表示无符号和长整型。后缀可以是大写或小写,且顺序不限。
以下是一些整数文字的示例 -
212 // 合法 215u // 合法 0xFeeL // 合法 078 // 非法:8 不是八进制数字 032UU // 非法:后缀不能重复
以下是其他各种整数文字的示例 -
85 // 十进制 0213 // 八进制 0x4b // 十六进制 30 // 整数 30u // 无符号整数 30l // 长整型 30ul // 无符号长整型
浮点数字面量
浮点字面量包含整数部分、小数点、小数部分和指数部分。浮点字面量可以用十进制或指数形式表示。
使用十进制表示时,必须包含小数点、指数或两者;使用指数表示时,必须包含整数部分、小数部分或两者。有符号指数由 e 或 E 引入。
以下是一些浮点文字的示例 -
3.14159 // 合法 314159E-5L // 合法 510E // 非法:指数不完整 210f // 非法:无小数或指数 .e55 // 非法:缺少整数或分数
布尔字面量
布尔字面量有两个,它们是标准 C++ 关键字的一部分 -
值为 true 表示真。
值为 false 表示假。
不应将 true 的值视为 1,将 false 的值视为 0。
字符字面量
字符字面量用单引号括起来。如果字面量以 L(仅限大写)开头,则为宽字符字面量(例如,L'x'),应存储在 wchar_t 类型的变量中。否则,它是一个窄字符字面量(例如,'x'),可以存储在一个 char 类型的简单变量中。
字符字面量可以是普通字符(例如,'x')、转义序列(例如,' ')或通用字符(例如,'\u02C0')。
在 C++ 中,某些字符前面带有反斜杠时具有特殊含义,它们用于表示换行符 ( ) 或制表符 ( )。这里列出了一些这样的转义序列代码 -
转义序列 | 含义 |
---|---|
\ | \ 字符 |
\' | ' 字符 |
\" | " 字符 |
\? | ?字符 |
\a | 提醒或铃声 |
\b | 退格键 |
\f | 换页符 |
换行符 | |
回车符 | |
水平制表符 | |
\v | 垂直制表符 |
\ooo | 一到三位数字的八进制数 |
\xhh . . . | 一位或多位数字的十六进制数 |
示例
以下示例展示了一些转义序列字符 -
#include <iostream> using namespace std; int main() { cout << "Hello World "; 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 ' ' 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 = ' '; int area; area = LENGTH * WIDTH; cout << area; cout << NEWLINE; return 0; }
当编译并执行上述代码时,它会产生以下结果 -
50
请注意,用大写字母定义常量是一种良好的编程习惯。
C++ 修饰符类型
C++ 允许在 char、int 和 double 数据类型前添加修饰符。修饰符用于改变基类型的含义,使其更精确地满足各种情况的需求。
数据类型修饰符列表如下 -
- signed
- unsigned
- long
- short
修饰符 signed、unsigned、long 和 short 可用于整数基类型。此外,signed 和 unsigned 可以应用于 char 类型,long 可以应用于 double 类型。
修饰符 signed 和 unsigned 也可以用作 long 或 short 修饰符的前缀。例如,unsigned long int。
C++ 允许使用简写形式声明 unsigned、short 或 long 整数。您可以直接使用 unsigned, short, 或 long, 单词,而不使用 int。它自动隐含了 int。例如,以下两个语句都声明了无符号整数变量。
unsigned x; unsigned int y;
要了解 C++ 解释有符号和无符号整数修饰符的方式之间的区别,您应该运行以下简短程序 -
#include <iostream> using namespace std; /* 此程序展示了以下两者之间的区别 * 有符号整数和无符号整数。 */ int main() { short int i; // 有符号短整数 short unsigned int j; // 无符号短整数 j = 50000; i = j; cout << i << " " << j; return 0; }
运行此程序时,输出结果如下:
-15536 50000
上述结果是因为将 50,000 表示为短无符号整数的位模式被短整型解释为 -15,536。
C++ 中的类型限定符
类型限定符提供有关其前面的变量的附加信息。
Sr.No | 限定符 &含义 |
---|---|
1 | const 程序在执行期间无法更改const类型的对象。 |
2 | volatile 修饰符volatile告诉编译器,变量的值可能会以程序未明确指定的方式被更改。 |
3 | restrict 最初,只有通过restrict限定的指针才能访问其指向的对象。只有 C99 添加了一个名为 restrict 的新类型限定符。 |
C++ 中的存储类
存储类定义了 C++ 程序中变量和/或函数的作用域(可见性)和生命周期。这些说明符位于它们修饰的类型之前。C++ 程序中可以使用以下存储类
- auto
- register
- static
- extern
- mutable
auto 存储类
auto 存储类是所有局部变量的默认存储类。
示例
以下是 auto 存储类的示例 -
{ int mount; auto int month; }
上述示例定义了两个具有相同存储类型的变量,auto 只能在函数内部使用,即局部变量。
寄存器存储类型
寄存器存储类型用于定义应存储在寄存器而非 RAM 中的局部变量。这意味着该变量的最大大小等于寄存器大小(通常为一个字),并且不能对其应用一元运算符"&"(因为它没有实际的内存位置)。
示例
以下是寄存器存储类型的示例 -
{ register int miles; }
寄存器仅应用于需要快速访问的变量,例如计数器。还应注意,定义"寄存器"并不意味着变量将存储在寄存器中。这意味着,根据硬件和实现限制,变量可能会存储在寄存器中。
静态存储类
static 存储类指示编译器在程序的整个生命周期内保持局部变量的存在,而不是在变量每次进入和离开作用域时创建和销毁它。因此,将局部变量设置为静态可以使它们在函数调用之间保持其值。
static 修饰符也可以应用于全局变量。执行此操作后,该变量的作用域将被限制在其声明的文件中。
在 C++ 中,当对类数据成员使用 static 时,该成员的副本将仅由其类的所有对象共享。
示例
以下是静态存储类的示例 -
#include <iostream> // 函数声明 void func(void); static int count = 10; /* 全局变量 */ main() { while(count--) { func(); } return 0; } // 函数定义 void func( void ) { static int i = 5; // 局部静态变量 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 修饰符,如下所述。
示例
以下是 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 成员函数修改。
C++ 中的运算符
运算符是指示编译器执行特定数学或逻辑运算的符号。 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 |
示例
以下是算术运算符的示例 -
#include <iostream> using namespace std; main() { int a = 21; int b = 10; int c ; c = a + b; cout << "Line 1 - Value of c is :" << c << endl ; c = a - b; cout << "Line 2 - Value of c is :" << c << endl; c = a * b; cout << "Line 3 - Value of c is :" << c << endl ; c = a / b; cout << "Line 4 - Value of c is :" << c << endl ; c = a % b; cout << "Line 5 - Value of c is :" << c << endl ; c = a++; cout << "Line 6 - Value of c is :" << c << endl ; c = a--; cout << "Line 7 - Value of c is :" << c << endl ; return 0; }
输出
Line 1 - Value of c is :31 Line 2 - Value of c is :11 Line 3 - Value of c is :210 Line 4 - Value of c is :2 Line 5 - Value of c is :1 Line 6 - Value of c is :21 Line 7 - Value of c is :22
关系运算符
关系运算符用于比较两个值或表达式。这些运算符返回布尔值 - 如果比较正确,则返回 true;否则返回 false。
它们对于根据条件进行决策和控制程序流程至关重要。
C++ 语言支持以下关系运算符
假设变量 A 为 10,变量 B 为 20,则 -
运算符 | 描述 | 示例 |
---|---|---|
== | 检查两个操作数的值是否相等,如果相等,则条件变为真。 | (A == B) 不为真。 |
!= | 检查两个操作数的值是否相等,如果不相等,则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是,则条件为真。 | (A > B) 不为真。 |
< | 检查左操作数的值是否小于右操作数的值,如果是,则条件成立。 | (A < B) 为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是,则条件成立。 | (A >= B) 不为真。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是,则条件成立真。 | (A <= B) 为真。 |
示例
以下是关系运算符的示例 -
#include <iostream> using namespace std; main() { int a = 21; int b = 10; int c ; if( a == b ) { cout << "Line 1 - a is equal to b" << endl ; } else { cout << "Line 1 - a is not equal to b" << endl ; } if( a < b ) { cout << "Line 2 - a is less than b" << endl ; } else { cout << "Line 2 - a is not less than b" << endl ; } if( a > b ) { cout << "Line 3 - a is greater than b" << endl ; } else { cout << "Line 3 - a is not greater than b" << endl ; } /* 让我们改变 a 和 b 的值 */ a = 5; b = 20; if( a <= b ) { cout << "Line 4 - a is either less than \ or equal to b" << endl ; } if( b >= a ) { cout << "Line 5 - b is either greater than \ or equal to b" << endl ; } return 0; }
输出
Line 1 - a is not equal to b Line 2 - a is not less than b Line 3 - a is greater than b Line 4 - a is either less than or equal to b Line 5 - b is either greater than or equal to b
逻辑运算符
逻辑运算符用于对布尔值(true 或 false)执行逻辑运算。这些运算符对于根据条件控制程序流程至关重要。 C++ 中有三个主要的逻辑运算符,如下所示:-
C++ 语言支持以下逻辑运算符。
假设变量 A 为 1,变量 B 为 0,则 -
运算符 | 描述 | 示例 |
---|---|---|
&& | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 |
|| | 称为逻辑或运算符。如果两个操作数中有一个非零,则条件为真。 | (A || B) 为真。 |
! | 称为逻辑非运算符。用于反转其操作数的逻辑状态。如果条件为真,则逻辑非运算符将返回假。 | !(A && B) 为真。 |
示例
以下是逻辑运算符的示例 -
#include <iostream> using namespace std; main() { int a = 5; int b = 20; int c ; if(a && b) { cout << "Line 1 - Condition is true"<< endl ; } if(a || b) { cout << "Line 2 - Condition is true"<< endl ; } /* 让我们改变 a 和 b 的值 */ a = 0; b = 10; if(a && b) { cout << "Line 3 - Condition is true"<< endl ; } else { cout << "Line 4 - Condition is not true"<< endl ; } if(!(a && b)) { cout << "Line 5 - Condition is true"<< endl ; } return 0; }
输出
Line 1 - Condition is true Line 2 - Condition is true Line 4 - Condition is not true Line 5 - Condition is true
位运算符
位运算符用于对整数数据类型执行位级运算。这些运算适用于直接操作位,例如低级编程、图形和加密。
位运算符作用于位,并执行逐位运算。&、| 和 ^ 的真值表如下 -
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 |
示例
以下是按位运算符的示例 -
#include <iostream> using namespace std; main() { unsigned int a = 60; // 60 = 0011 1100 unsigned int b = 13; // 13 = 0000 1101 int c = 0; c = a & b; // 12 = 0000 1100 cout << "Line 1 - Value of c is : " << c << endl ; c = a | b; // 61 = 0011 1101 cout << "Line 2 - Value of c is: " << c << endl ; c = a ^ b; // 49 = 0011 0001 cout << "Line 3 - Value of c is: " << c << endl ; c = ~a; // -61 = 1100 0011 cout << "Line 4 - Value of c is: " << c << endl ; c = a << 2; // 240 = 1111 0000 cout << "Line 5 - Value of c is: " << c << endl ; c = a >> 2; // 15 = 0000 1111 cout << "Line 6 - Value of c is: " << c << endl ; return 0; }
输出
Line 1 - Value of c is : 12 Line 2 - Value of c is: 61 Line 3 - Value of c is: 49 Line 4 - Value of c is: -61 Line 5 - Value of c is: 240 Line 6 - Value of c is: 15
赋值运算符
赋值运算符用于为变量赋值。这些运算符允许您设置或更新变量中存储的值。
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 |
示例
以下是赋值运算符的示例 -
#include <iostream> using namespace std; main() { int a = 21; int c ; c = a; cout << "Line 1 - = Operator, Value of c = : " <<c<< endl ; c += a; cout << "Line 2 - += Operator, Value of c = : " <<c<< endl ; c -= a; cout << "Line 3 - -= Operator, Value of c = : " <<c<< endl ; c *= a; cout << "Line 4 - *= Operator, Value of c = : " <<c<< endl ; c /= a; cout << "Line 5 - /= Operator, Value of c = : " <<c<< endl ; c = 200; c %= a; cout << "Line 6 - %= Operator, Value of c = : " <<c<< endl ; c <<= 2; cout << "Line 7 - <<= Operator, Value of c = : " <<c<< endl ; c >>= 2; cout << "Line 8 - >>= Operator, Value of c = : " <<c<< endl ; c &= 2; cout << "Line 9 - &= Operator, Value of c = : " <<c<< endl ; c ^= 2; cout << "Line 10 - ^= Operator, Value of c = : " <<c<< endl ; c |= 2; cout << "Line 11 - |= Operator, Value of c = : " <<c<< endl ; return 0; }
输出
Line 1 - = Operator, Value of c = : 21 Line 2 - += Operator, Value of c = : 42 Line 3 - -= Operator, Value of c = : 21 Line 4 - *= Operator, Value of c = : 441 Line 5 - /= Operator, Value of c = : 21 Line 6 - %= Operator, Value of c = : 11 Line 7 - <<= Operator, Value of c = : 44 Line 8 - >>= Operator, Value of c = : 11 Line 9 - &= Operator, Value of c = : 2 Line 10 - ^= Operator, Value of c = : 0 Line 11 - |= Operator, Value of c = : 2
杂项运算符
杂项运算符,通常缩写为"杂项运算符",包含各种无法归入算术运算符或逻辑运算符等其他类别的运算符。以下是一些常见的杂项运算符及其定义 -
下表列出了 C++ 支持的一些其他运算符。
序号 | 运算符及说明 |
---|---|
1 | sizeof sizeof 运算符返回变量的大小。例如,sizeof(a),其中 a 为整数,将返回 4。 |
2 | 条件 ? X : Y 条件运算符 (?)。如果条件为真,则返回 X 的值,否则返回 Y 的值。 |
3 | , 逗号运算符用于执行一系列操作。整个逗号表达式的值等于逗号分隔列表中最后一个表达式的值。 |
4 | .(点)和 ->(箭头) 成员运算符用于引用类、结构体和联合体中的各个成员。 |
5 | 强制类型转换 强制类型转换运算符用于将一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。 |
6 | & 指针运算符 & 返回变量的地址。例如,&a; 将返回变量的实际地址。 |
7 | * 指针运算符 * 是指向变量的指针。例如,*var; 将指向变量 var。 |
条件(三元)运算符 (?:)
这是在 C++ 中执行条件求值的一种简便方法。
int a = 10, b = 20; int result = (a > b) ? a : b; // 结果为 20,因为 a 不大于 b cout << "较大的值为:" << result << endl;
输出
较大的值为:20
Sizeof 运算符
Sizeof 运算符返回变量或数据类型的大小(以字节为单位)。
int x = 5; cout << "Size of int: " << sizeof(x) << " bytes" << endl; cout << "Size of double: " << sizeof(double) << " bytes" << endl;
输出
4 8
按位求补运算符 (~)
按位求补运算符将其操作数的位取反。
unsigned int num = 5; // 二进制:00000000 00000000 00000000 00000101 unsigned int result = ~num; // 二进制:11111111 11111111 11111111 11111010 cout << "5 的按位求补:" << result << endl; // 由于无符号,输出一个较大的正数
作用域解析运算符 (::)
此运算符用于定义函数或变量的作用域,尤其是在类和命名空间的上下文中。
class MyClass { public: static int value; }; int MyClass::value = 10; // 在类外部定义静态成员 cout << "Static value: " << MyClass::value << endl;
输出
10
C++ 中的运算符优先级
运算符优先级决定了表达式中项的分组。这会影响表达式的求值方式。某些运算符的优先级高于其他运算符;例如,乘法运算符的优先级高于加法运算符 -
例如 x = 7 + 3 * 2;这里,x 被赋值为 13,而不是 20,因为运算符 * 的优先级高于 +,所以它先与 3*2 相乘,然后再与 7 相加。
此处,优先级最高的运算符显示在表格顶部,优先级最低的运算符显示在表格底部。在表达式中,优先级较高的运算符将首先求值。
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . ++ - - | 从左到右 |
一元运算符 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘法 | * / % | 从左到右 |
加法 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
按位与 | & | 从左到右 |
按位异或 | ^ | 从左到右 |
按位或 | | | 从左到右 |
逻辑与 | && | 从左到右 |
逻辑或 | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %=>>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |
C++ 循环类型
有时可能需要多次执行一段代码。通常,语句是按顺序执行的:函数中的第一个语句先执行,然后是第二个,依此类推。
编程语言提供了各种控制结构,可以实现更复杂的执行路径。
循环语句允许我们多次执行一个语句或一组语句,以下是大多数编程语言中循环语句的一般形式:

C++ 编程语言提供了以下类型的循环来处理循环需求。
Sr.No | 循环类型 &说明 |
---|---|
1 | while 循环
当给定条件为真时,重复执行一个语句或一组语句。它在执行循环体之前测试条件。 |
2 | for 循环
多次执行一系列语句,并简化管理循环变量的代码。 |
3 | do...while 循环
类似于 while 语句,但它在循环体末尾测试条件。 |
4 | 嵌套循环
您可以在任何其他 while、for 或 do..while 循环中使用一个或多个循环。 |
循环控制语句
循环控制语句会改变执行顺序。当执行离开某个作用域时,在该作用域中创建的所有自动对象都将被销毁。
C++ 支持以下控制语句。
Sr.No | 控制语句 &说明 |
---|---|
1 | break 语句
终止 loop 或 switch 语句,并将执行转移到紧接着该循环或 switch 语句的语句。 |
2 | continue 语句
使循环跳过其主体的剩余部分,并在重新迭代之前立即重新测试其条件。 |
3 | goto 语句
将控制权转移到带标签的语句。虽然不建议在程序中使用 goto 语句。 |
无限循环
如果条件永不成立,则循环将变为无限循环。for 循环通常用于此目的。由于构成 for 循环的三个表达式都不是必需的,因此可以通过将条件表达式留空来创建无限循环。
#include <iostream> using namespace std; int main () { for( ; ; ) { printf("This loop will run forever."); } return 0; }
当条件表达式不存在时,假定其为真。您可能有一个初始化和增量表达式,但 C++ 程序员更常用 for (;;) 结构来表示无限循环。
注意 − 您可以通过按 Ctrl + C 键来终止无限循环。
C++ 决策语句
决策结构要求程序员指定程序要评估或测试的一个或多个条件,以及在条件成立时要执行的一个或多个语句,以及(可选)在条件不成立时要执行的其他语句。
以下是大多数编程语言中常见的典型决策结构的一般形式 -

C++ 编程语言提供以下类型的决策语句。
Sr.No | 语句 &说明 |
---|---|
1 | if 语句
if 语句由一个布尔表达式后跟一个或多个语句组成。 |
2 | if...else 语句
if 语句后可以跟一个可选的 else 语句,当布尔表达式为 false 时执行该语句。 |
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,并返回其中的最大值 −
// 函数返回两个数字中的最大值 int max(int num1, int num2) { // 局部变量声明 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++ 函数时,你需要定义该函数的功能。要使用函数,你必须调用或调用该函数。
当程序调用函数时,程序控制权将转移到被调用函数。被调用函数执行定义的任务,当执行到其 return 语句或到达函数结束的右括号时,它将程序控制权返回给主程序。
要调用函数,你只需传递所需的参数以及函数名称;如果函数返回值,则可以存储返回值。例如 -
#includeusing namespace std; // 函数声明 int max(int num1, int num2); int main () { // 局部变量声明: int a = 100; int b = 200; int ret; // 调用函数来获取最大值。 ret = max(a, b); cout << "Max value is : " << ret << endl; return 0; } // 返回两个数字之间的最大值的函数 int max(int num1, int num2) { // 局部变量声明 int result; if (num1 > num2) result = num1; else result = num2; return result; }
我将 max() 函数与 main() 函数一起保留,并编译了源代码。运行最终可执行文件时,将产生以下结果 -
Max value is : 200
函数参数
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数的行为类似于函数内部的其他局部变量,在进入函数时创建,在退出时销毁。
调用函数时,有两种方式将参数传递给函数 -
Sr.No | 调用类型 &说明 |
---|---|
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 () { // 局部变量声明: int a = 100; int b = 200; int result; // 调用函数来添加值。 result = sum(a, b); cout << "Total value is :" << result << endl; // 再次调用函数如下。 result = sum(a); cout << "Total value is :" << result << endl; return 0; }
当编译并执行上述代码时,它会产生以下结果 -
Total value is :300 Total value is :120
C++ 中的数字
通常,我们使用数字时,会使用原始数据类型,例如 int、short、long、float 和 double 等。在讨论 C++ 数据类型时,我们已经解释了数字数据类型、它们的可能值以及数值范围。
在 C++ 中定义数字
您已经在前几章给出的各种示例中定义了数字。这里是另一个在 C++ 中定义各种数字类型的综合示例 -
#include <iostream> using namespace std; int main () { // 数字定义: short s; int i; long l; float f; double d; // 数字赋值; s = 10; i = 1000; l = 1000000; f = 230.47; d = 30949.374; // 数字打印; 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++ 中一些实用的内置数学函数。
要使用这些函数,您需要包含数学头文件 <cmath>。
Sr.No | 函数 &用途 |
---|---|
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 () { // 数字定义: short s = 10; int i = -1000; long l = 100000; float f = 230.47; double d = 200.374; // 数学运算; 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; // 设置种子 srand( (unsigned)time( NULL ) ); /* 生成 10 个随机数。*/ for( i = 0; i < 10; i++ ) { // 生成实际的随机数 j = rand(); cout <<" 随机数: " << j << endl; } return 0; }
当编译并执行上述代码时,它会产生以下结果 -
随机数: 1748144778 随机数: 630873888 随机数: 2134540646 随机数: 219404170 随机数: 902129458 随机数: 920445370 随机数: 1319072661 随机数: 257938873 随机数: 1256201101 随机数: 580322989
C++ 数组
C++ 数组存储相同类型元素的固定大小的顺序集合。数组用于存储数据集合,但通常将数组视为相同类型的变量的集合更为实用。
与其声明单个变量(例如 number0、number1、... 和 number99),不如声明一个数组变量(例如 numbers),并使用 numbers[0]、numbers[1] 和 ... numbers[99] 来表示单个变量。数组中的特定元素通过索引访问。
所有数组都由连续的内存位置组成。最低地址对应第一个元素,最高地址对应最后一个元素。
声明数组
要在 C++ 中声明数组,程序员需要指定元素的类型以及数组所需的元素数量,如下所示:
type arrayName [ arraySize ];
这称为一维数组。arraySize 必须是大于零的整型常量,type 可以是任何有效的 C++ 数据类型。例如,要声明一个名为 balance 且类型为 double 的 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。具有第 4 个索引的数组将是第 5 个元素,即最后一个元素,因为所有数组的第一个元素的索引都是 0,也称为基索引。以下是我们上面讨论的相同数组的图形表示 -

访问数组元素
通过索引数组名称来访问元素。方法是将元素的索引放在数组名称后的方括号中。例如 -
double salary = balance[9];
上述语句将从数组中取出第 10 个元素,并将其值赋给 salary 变量。下面是一个示例,它将使用上述三个概念:声明、赋值和访问数组 -
示例
在下面的示例中,我们声明一个数组,为该数组赋值,然后访问数组元素 -
#include <iostream> using namespace std; #include <iomanip> using std::setw; int main () { int n[ 10 ]; // n 是一个包含 10 个整数的数组 // 将数组 n 的元素初始化为 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; // 输出每个数组元素的值 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
获取数组长度
要获取数组的长度,可以使用 sizeof() 运算符,即用数组的大小除以数组元素的大小。
请考虑以下语法 -
length = sizeof(arr) / sizeof(arr[0]);
示例
在下面的示例中,我们定义一个数组并计算其长度 -
#include <iostream> using namespace std; int main() { int arr[] = {10, 20, 30, 40, 50}; int arr_length = sizeof(arr) / sizeof(arr[0]); cout << "Array's Length : " << arr_length; return 0; }
输出
Array's Length : 5
更改数组元素
您可以通过指定数组元素的索引并赋值来更改其值。
示例
在下面的示例中,我们将更改索引 2 处的值 -
#include <iostream> using namespace std; int main() { int arr[] = {10, 20, 30, 40, 50}; cout << "Before changing, element at index 2: " << arr[2] << endl; // 改变值 arr[2] = 108; cout << "After changing, element at index 2: " << arr[2] << endl; return 0; }
输出
Before changing, element at index 2: 30 After changing, element at index 2: 108
更多关于 C++ 数组的内容
数组对 C++ 至关重要,需要更多细节的介绍。以下是一些重要的概念,C++ 程序员应该清楚了解它们 -
序号 | 概念与描述 |
---|---|
1 | 多维数组
C++ 支持多维数组。多维数组最简单的形式是二维数组。 |
2 | 指向数组的指针
只需指定数组名称(无需索引)即可生成指向数组首元素的指针。 |
3 | 将数组传递给函数
只需指定数组名称(无需索引)即可将指向数组的指针传递给函数。 |
4 | 从函数返回数组
C++ 允许函数返回数组。 |
C++ 字符串
C++ 提供以下两种类型的字符串表示 -
- C 风格的字符串。
- 标准 C++ 中引入的 string 类类型。
C 风格的字符串
C 风格的字符串起源于 C 语言,并继续在 C++ 中得到支持。该字符串实际上是一个一维字符数组,以 空 字符 '\0' 结尾。因此,以空字符结尾的字符串包含组成该字符串的字符,后跟一个 空。
以下声明和初始化创建了一个由单词"Hello"组成的字符串。为了将空字符保存在数组末尾,包含该字符串的字符数组的大小应比单词"Hello"的字符数多一个。
char greet[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
如果遵循数组初始化规则,则可以将上述语句编写如下 -
char greet[] = "Hello";
以下是上述定义的字符串在 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 风格字符串函数
C++ 支持多种操作以空字符结尾的字符串的函数。这些函数定义在 <string.h> 头文件 中。
Sr.No | 函数 &用途 |
---|---|
1 |
将字符串 s2 复制到字符串 s1 中。 |
2 |
将字符串 s2 连接到字符串 s1 的末尾。 |
3 |
返回字符串 s1 的长度。 |
4 |
如果 s1 和 s2 相同,则返回 0;如果 s1 |
5 |
返回指向字符串 s1 中字符 ch 首次出现的指针。 |
6 |
返回指向字符串 s2 首次出现的指针s1. |
C++ 引用
引用变量是一个别名,即已存在变量的另一个名称。一旦使用变量初始化引用,就可以使用变量名或引用名来引用该变量。
引用与指针
引用经常与指针混淆,但引用和指针之间有三个主要区别:
不能使用 NULL 引用。必须始终能够假定引用连接到合法的存储空间。
一旦将引用初始化为一个对象,就不能将其更改为引用另一个对象。指针可以随时指向另一个对象。
引用必须在创建时初始化。指针可以随时初始化。
在 C++ 中创建引用
可以将变量名视为附加在变量内存位置上的标签。然后,您可以将引用视为附加在该内存位置上的第二个标签。因此,您可以通过原始变量名或引用来访问变量的内容。例如,假设我们有以下示例 -
int i = 17;
我们可以按如下方式为 i 声明引用变量。
int& r = i;
将这些声明中的 & 读作 reference。因此,将第一个声明理解为"r 是一个初始化为 i 的整数引用",将第二个声明理解为"s 是一个初始化为 d 的双精度引用"。以下示例使用了 int 和 double 的引用 -
#include <iostream> using namespace std; int main () { // 声明简单变量 int i; double d; // 声明引用变量 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++ 程序员应该清楚了解它们 -
C++ 日期和时间
C++ 标准库没有提供合适的日期类型。C++ 继承了 C 语言中用于日期和时间操作的结构体和函数。要访问与日期和时间相关的函数和结构体,您需要在 C++ 程序中包含 <ctime> 头文件。
有四种与时间相关的类型:clock_t、time_t、size_t 和 tm。 clock_t、size_t 和 time_t 类型能够将系统时间和日期表示为某种整数。
结构体类型 tm 以 C 结构体 的形式保存日期和时间,该结构体包含以下元素:
struct tm { int tm_sec; // 分钟中的秒数,范围从 0 到 61 int tm_min; // 小时中的分钟数,范围从 0 到 59 int tm_hour; // 小时中的小时数,范围从 0 到 24 int tm_mday; // 月份中的日期,范围从 1 到 31 int tm_mon; // 月份,范围从 0 到 11 int tm_year; // 自 1900 年以来的年份 int tm_wday; // 自星期日以来的天数 int tm_yday; // 自 1 月 1 日以来的天数 int tm_isdst; // 夏令时小时数 }
以下是在 C 或 C++ 中处理日期和时间时使用的重要函数。所有这些函数都是标准 C 和 C++ 库的一部分,您可以参考下面提供的 C++ 标准库来查看它们的详细信息。
Sr.No | 函数 &用途 |
---|---|
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 \0 的字符串的指针。 |
3 | struct tm *localtime(const time_t *time); 这将返回一个指向表示本地时间的 tm 结构的指针。 |
4 | clock_t clock(void); 这将返回一个近似值,该值表示调用程序已运行的时间。如果时间不可用,则返回值 .1。 |
5 | char * asctime ( const struct tm * time ); 这将返回一个指向字符串的指针,该字符串包含 time 指向的结构体中存储的信息,并转换为以下形式:日 月 日 时:分:秒 年 \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() { // 基于当前系统的当前日期/时间 time_t now = time(0); // 将 now 转换为字符串形式 char* dt = ctime(&now); cout << "The local date and time is: " << dt << endl; // 现在转换为 UTC 的 tm 结构 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
使用 tm 结构体格式化时间
在 C 或 C++ 中处理日期和时间时,tm 结构体非常重要。如上所述,该结构体以 C 结构体的形式保存日期和时间。大多数与时间相关的函数都使用 tm 结构体。以下示例使用了各种与日期和时间相关的函数以及 tm 结构体 -
在本章中使用结构体时,我假设您对 C 结构体以及如何使用箭头 -> 运算符访问结构体成员有基本的了解。
示例
#include <iostream> #include <ctime> using namespace std; int main() { // 基于当前系统的当前日期/时间 time_t now = time(0); cout << "Number of sec since January 1,1970 is:: " << now << endl; tm *ltm = localtime(&now); // 打印 tm 结构的各个组成部分。 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++ 程序很重要 -
Sr.No | 头文件 &函数和说明 |
---|---|
1 |
此文件定义了cin、clog和clog对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。 |
2 |
此文件声明了用于执行格式化 I/O 的服务,这些服务使用所谓的参数化流操作符,例如 setw 和 setprecision。 |
3 |
此文件声明了用户控制文件处理的服务。我们将在"文件和流"相关章节中详细讨论它。 |
4 |
<bits/stdc++.h> 此头文件包含大多数标准 C++ 库,无需单独指定每个库即可添加各种功能。这在编程竞赛中尤其有用。 |
标准输出流 (cout)
预定义对象 cout 是 ostream 类的一个实例。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)
预定义对象 cin 是 istream 类的一个实例。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; }
当上述代码编译并执行时,它会提示您输入一个名称。您输入一个值,然后按回车键,即可看到以下结果:
Please enter your name: cplusplus Your name is: cplusplus
C++ 编译器还会判断输入值的数据类型,并选择合适的流提取运算符来提取该值并将其存储在给定的变量中。
流提取运算符 >> 可以在单个语句中多次使用。要请求多个数据,可以使用以下命令:
cin >> name >> age;
这等同于以下两个语句:
cin >> name; cin >> age;
标准错误流 (cerr)
预定义对象 cerr 是 ostream 类的一个实例。 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)
预定义对象 clog 是 ostream 类的一个实例。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。
使用 bit/stdc++ 进行输入/输出
bits/stdc++.h 是 C++ 中的非标准头文件,因为它不属于官方 C++ 标准库。相反,它是一个 GCC 特定的头文件,包含了 C++ 中的大多数标准库。您也可以使用 bit/stdc++.h 进行输入和输出,而无需使用其他库。
示例
#include <bits/stdc++.h> using namespace std; int main() { int number; string name; // #include <iostream> // #include <string> cout << "Welcome to TutorialsPoint!" << endl; // 输入用户姓名和号码 cout << "Please enter your name: "; cin >> name; cout << "Please enter a number: "; cin >> number; cout << "Hello," << name << " You entered " << number << endl; // 演示一些 STL 特性 // #include <vector> vector<int> numbers; cout << "Enter 4 numbers: "; for (int i = 0; i < 4; ++i) { int temp; cin >> temp; numbers.push_back(temp); } cout << "You entered the following numbers: "; for (int num : numbers) { cout << num << " "; } cout << endl; // 排序并显示数字 // #include <algorithm> sort(numbers.begin(), numbers.end()); cout << "Sorted numbers: "; for (int num : numbers) { cout << num << " "; } cout << endl; return 0; }
当编译并执行上述代码时,它会产生以下结果 -
Welcome to TutorialsPoint! Please enter your name: Aman Please enter a number: 2006 Hello, Aman You 2006 Enter 4 numbers: 2 0 0 6 You entered the following numbers: 2 0 0 6 Sorted numbers: 0 0 2 6
因此,我们无需使用诸如
- #include <iostream>
- #include <string>
- #include <vector>
- #include <algorithm>
我们直接使用了 <bits/stdc++.h> ,因为它包含了输入/输出操作、字符串处理、动态数组和算法所需的所有标准库,从而简化了代码,使其更简洁、更方便。它尤其适用于竞技编程。
C++ 结构体 (struct)
C++ 结构体是用户定义的数据类型,用于将不同类型的相关变量组合到一个名称下。结构体也称为 structs。
结构体用于表示一条记录,假设您想跟踪图书馆中的图书。您可能需要跟踪每本书的以下属性 -
- Title
- Author
- Subject
- Book 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; // 将 Book1 声明为 Book 类型 struct Books Book2; // 将 Book2 声明为 Book 类型 // 书籍 1 的规范 strcpy( Book1.title, "Learn C++ Programming"); strcpy( Book1.author, "Chand Miyan"); strcpy( Book1.subject, "C++ Programming"); Book1.book_id = 6495407; // 书籍 2 的规范 strcpy( Book2.title, "Telecom Billing"); strcpy( Book2.author, "Yakit Singha"); strcpy( Book2.subject, "Telecom"); Book2.book_id = 6495700; // 打印 Book1 信息 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; // 打印 Book2 信息 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; // 将 Book1 声明为 Book 类型 struct Books Book2; // 将 Book2 声明为 Book 类型 // 书籍 1 的规范 strcpy( Book1.title, "Learn C++ Programming"); strcpy( Book1.author, "Chand Miyan"); strcpy( Book1.subject, "C++ Programming"); Book1.book_id = 6495407; // 书籍 2 的规范 strcpy( Book2.title, "Telecom Billing"); strcpy( Book2.author, "Yakit Singha"); strcpy( Book2.subject, "Telecom"); Book2.book_id = 6495700; // 打印 Book1 信息 printBook( Book1 ); // 打印 Book2 信息 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; // 将 Book1 声明为 Book 类型 struct Books Book2; // 将 Book2 声明为 Book 类型 // Book 1 的说明 strcpy( Book1.title, "Learn C++ Programming"); strcpy( Book1.author, "Chand Miyan"); strcpy( Book1.subject, "C++ Programming"); Book1.book_id = 6495407; // Book 2 的说明 strcpy( Book2.title, "Telecom Billing"); strcpy( Book2.author, "Yakit Singha"); strcpy( Book2.subject, "Telecom"); Book2.book_id = 6495700; // 打印 Book1 的信息,传递结构体的地址 printBook( &Book1 ); // 打印 Book1 的信息,传递结构体的地址 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
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; // box 盒子的长度 double breadth; // box 盒子的宽度 double height; // box 盒子的高度 };
关键字 public 决定了其后类成员的访问属性。公共成员可以在类对象作用域内的任何位置从类外部访问。您还可以将类的成员指定为 private 或 protected,我们将在下一节中讨论。
定义 C++ 对象
类提供了对象的蓝图,因此对象基本上是由类创建的。我们声明类对象的声明方式与声明基本类型变量的声明方式完全相同。以下语句声明了两个 Box 类的对象 -
Box Box1; // 声明 Box1 为 Box 类型 Box Box2; // 将 Box2 声明为 Box 类型
Box1 和 Box2 对象都将拥有各自的数据成员副本。
访问数据成员
可以使用直接成员访问运算符 (.) 访问类对象的公共数据成员。让我们尝试以下示例来更清楚地理解 -
#include <iostream> using namespace std; class Box { public: double length; // box 盒子的长度 double breadth; // box 盒子的宽度 double height; // box 盒子的高度 }; int main() { Box Box1; // 声明 Box1 为 Box 类型 Box Box2; // 声明 Box2 为 Box 类型 doublevolume = 0.0; // 在此处存储盒子的体积 // box 1 规范 Box1.height = 5.0; Box1.length = 6.0; Box1.breadth = 7.0; // box 2 规范 Box2.height = 10.0; Box2.length = 12.0; Box2.breadth = 13.0; // box 1 的体积 volume = Box1.height * Box1.length * Box1.breadth; cout << "Box1 的体积:" << volume <<endl; // box 2 的体积 volume = Box2.height * Box2.length * Box2.breadth; cout << "Box2 的体积:" << volume <<endl; return 0; }
当编译并执行上述代码时,它会产生以下结果 -
Box1 的体积:210 Box2 的体积:1560
需要注意的是,私有成员和受保护成员不能使用直接成员访问运算符 (.) 直接访问。我们将学习如何访问私有成员和受保护成员。
类和对象详解
到目前为止,您已经对 C++ 类和对象有了非常基本的了解。还有一些与 C++ 类和对象相关的有趣概念,我们将在下面列出的各个小节中讨论 -
Sr.No | 概念 &说明 |
---|---|
1 | 类成员函数
类的成员函数是指像其他变量一样,在类定义中拥有其定义或原型的函数。 |
2 | 类访问修饰符
类成员可以定义为 public、private 或 protected。默认情况下,成员会被假定为私有的。 |
3 | 构造函数和析构函数
类构造函数是类中的一个特殊函数,在创建该类的新对象时调用。析构函数也是一个特殊函数,在创建的对象被删除时调用。 |
4 | 复制构造函数
复制构造函数是一种构造函数,它通过使用先前创建的同一类的对象来初始化一个对象来创建对象。 |
5 | 友元函数
友元函数被允许完全访问类的私有和受保护成员。 |
6 | 内联函数
对于内联函数,编译器会尝试扩展函数体中的代码,而不是直接调用函数。 |
7 | this 指针
每个对象都有一个特殊的指针 this,它指向对象本身。 |
8 | 指向 C++ 类的指针
指向类的指针与指向结构的指针。实际上,类只是一个包含函数的结构体。 |
9 | 类的静态成员
类的数据成员和函数成员都可以声明为静态的。 |
C++ 继承
面向对象编程中最重要的概念之一是继承。继承允许我们根据另一个类来定义一个类,这使得应用程序的创建和维护更加容易。这也提供了重用代码功能和加快实现速度的机会。
创建类时,程序员可以指定新类继承现有类的成员,而无需编写全新的数据成员和成员函数。这个现有类称为基类,而新类称为派生类。
继承的思想实现了是关系。例如,哺乳动物是动物,狗是哺乳动物,因此狗也是动物,等等。
基类和派生类
一个类可以从多个类派生,这意味着它可以从多个基类继承数据和函数。要定义派生类,我们使用类派生列表来指定基类。类派生列表命名一个或多个基类,其形式为:-
class derived-class: access-specifier base-class
其中 access-specifier 是 public、protected 或 private 之一,base-class 是先前定义的类的名称。如果未使用访问说明符,则默认为私有。
考虑一个基类 Shape 及其派生类 Rectangle,如下所示 -
#include <iostream> using namespace std; // 基类 class Shape { public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 派生类 class Rectangle: public Shape { public: int getArea() { return (width * height); } }; int main(void) { Rectangle Rect; Rect.setWidth(5); Rect.setHeight(7); // 打印对象的面积。 cout << "Total area: " << Rect.getArea() << endl; return 0; }
当编译并执行上述代码时,它会产生以下结果 -
Total area: 35
访问控制与继承
派生类可以访问其基类的所有非私有成员。因此,派生类成员函数无法访问的基类成员应在基类中声明为私有成员。
我们可以根据访问对象来总结不同的访问类型,如下所示:
访问 | public | protected | private |
---|---|---|---|
Same class | yes | yes | yes |
Derived classes | yes | yes | no |
Outside classes | yes | no | no |
派生类继承所有基类方法,但以下方法除外:
- 基类的构造函数、析构函数和复制构造函数。
- 基类的重载运算符。
- 基类的友元函数。
继承类型
从基类派生类时,基类可以通过public、protected或private继承进行继承。继承类型由访问说明符指定,如上所述。
我们很少使用protected或private继承,但通常使用public继承。使用不同类型的继承时,应遵循以下规则 -
公有继承 - 从 public 基类派生类时,基类的 public 成员将成为派生类的 public 成员,而基类的 protected 成员将成为派生类的 protected 成员。基类的 private 成员永远无法从派生类直接访问,但可以通过调用基类的 public 和 protected 成员来访问。
受保护继承 − 从 protected 基类派生时,基类的 public 和 protected 成员将成为派生类的 protected 成员。
私有继承 − 从 private 基类派生时,基类的 public 和 protected 成员将成为派生类的 private 成员。
多重继承
C++ 类可以从多个继承成员使用多重继承的类。多重继承允许一个类从多个基类继承,这意味着派生类可以有多个父类,并从所有基类继承属性和行为。
扩展语法如下:
class derived-class: access baseA, access baseB....
其中 access 是 public、protected 或 private 之一,每个基类都会指定,它们之间用逗号分隔,如上所示。让我们尝试以下示例:
#include <iostream> using namespace std; // 基类 Shape class Shape { public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 基类 PaintCost class PaintCost { public: int getCost(int area) { return area * 70; } }; // 派生类 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(); // 打印对象的面积。 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++ 中的函数重载
在同一作用域内,同一个函数名可以有多个定义。函数的定义必须在参数列表的类型和/或参数数量上有所不同。不能对仅返回类型不同的函数声明进行重载。
阅读: 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; // 调用 print 打印整数 pd.print(5); // 调用 print 打印浮点数 pd.print(500.263); // 调用 print 打印字符 pd.print("Hello C++"); return 0; }
当编译并执行上述代码时,它会产生以下结果 -
Printing int: 5 Printing float: 500.263 Printing character: Hello C++
C++ 中的运算符重载
您可以重新定义或重载 C++ 中大多数内置运算符。因此,程序员也可以使用用户定义类型的运算符。
重载运算符是具有特殊名称的函数:关键字"operator"后跟所定义运算符的符号。与其他函数一样,重载运算符具有返回类型和参数列表。
Box 运算符 + (const Box&);
声明一个加法运算符,该运算符可用于将两个 Box 对象相加,并返回最终的 Box 对象。大多数重载运算符可以定义为普通的非成员函数或类成员函数。如果我们将上述函数定义为类的非成员函数,则必须为每个操作数传递两个参数,如下所示 -
Box 运算符 + (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; } // 重载 + 运算符以添加两个 Box 对象。 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; // box 盒子的长度 double breadth; // box 盒子的宽度 double height; // box 盒子的高度 }; // 程序的主函数 int main() { Box Box1; // 声明 Box1 为 Box 类型 Box Box2; // 声明 Box2 为 Box 类型 Box Box3; // 声明 Box3 为 Box 类型 doublevolume = 0.0; // 在此处存储盒子的体积 // box 1 规范 Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0); // box 2 规范 Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0); // box 1 的体积 volume = Box1.getVolume(); cout << "Box1 的体积:" << volume <<endl; // box 2 的体积 volume = Box2.getVolume(); cout << "Box2 的体积:" << volume <<endl; // 添加两个对象如下: Box3 = Box1 + Box2; // box 3 的体积 volume = Box3.getVolume(); cout << "Box3 的体积:" << volume <<endl; return 0; }
当编译并执行上述代码时,它会产生以下结果 -
Box1 的体积:210 Box2 的体积:1560 Box3 的体积:5400
可重载/不可重载运算符
以下是可重载的运算符列表 -
+ | - | * | / | % | ^ |
& | | | ~ | ! | , | = |
< | > | <= | >= | ++ | -- |
<< | >> | == | != | && | || |
+= | -= | /= | %= | ^= | &= |
|= | *= | <<= | >>= | [] | () |
-> | ->* | new | new [] | delete | delete [] |
以下是不能重载的运算符列表 -
:: | .* | . | ?: |
运算符重载示例
这里有各种运算符重载示例,可以帮助您理解这个概念。
序号 | 运算符 &示例 |
---|---|
1 | 一元运算符重载 |
2 | 二元运算符重载 |
3 | 关系运算符重载 |
4 | 输入/输出运算符重载 |
5 | ++ 和 -- 运算符重载 |
6 | 赋值运算符重载 |
7 | 函数 call () 运算符重载 |
8 | 下标 [] 运算符重载 |
9 | 类成员访问运算符 -> 重载 |
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 :" << width * height << endl; return width * height; } }; class Rectangle: public Shape { public: Rectangle( int a = 0, int b = 0):Shape(a, b) { } int area () { cout << "Rectangle class area :" << width * height << 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 :" << (width * height)/2 << endl; return (width * height / 2); } }; // 程序的主函数 int main() { Shape *shape; Rectangle rec(10,7); Triangle tri(10,5); // 存储 Rectangle 的地址 shape = &rec; // 调用矩形 area。 shape->area(); // 存储 Triangle 的地址 shape = &tri; // 调用三角形 area。 shape->area(); return 0; }
当编译并执行上述代码时,它会产生以下结果 -
Parent class area :70 Parent class area :50
输出错误的原因是,函数 area() 的调用被编译器设置为基类中定义的版本。这被称为函数调用的静态解析,或静态链接——函数调用在程序执行之前就已确定。有时,这也被称为早期绑定,因为 area() 函数是在程序编译期间设置的。
现在,让我们对程序稍作修改,在 Shape 类中 area() 的声明前加上关键字 virtual,使其如下所示 -
#include <iostream> using namespace std; 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 :" << width * height << endl; return width * height; } }; class Rectangle: public Shape { public: Rectangle( int a = 0, int b = 0):Shape(a, b) { } int area () { cout << "Rectangle class area :" << width * height << 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 :" << (width * height)/2 << endl; return (width * height / 2); } }; // 程序的主函数 int main() { Shape *shape; Rectangle rec(10,7); Triangle tri(10,5); // 存储 Rectangle 的地址 shape = &rec; // 调用矩形 area。 shape->area(); // 存储 Triangle 的地址 shape = &tri; // 调用三角形 area。 shape->area(); return 0; }
经过这一小段修改后,当编译并执行前面的示例代码时,它会产生以下结果 -
Rectangle class area :70 Triangle class area :25
这次,编译器会查看指针的内容,而不是它的类型。因此,由于 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; } // 纯虚函数 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: // 构造函数 Adder(int i = 0) { total = i; } // 外部接口 void addNum(int number) { total += number; } // 外部接口 int getTotal() { return total; }; private: // 隐藏来自外界的数据 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++ 程序都由以下两个基本元素组成 -
程序语句(代码) - 这是程序中执行操作的部分,它们被称为函数。
程序数据 - 数据是程序中受程序函数影响的信息。
封装是一种面向对象编程概念,它将数据和操作数据的函数绑定在一起,并确保两者免受外界干扰和滥用。数据封装引出了面向对象编程 (OOP) 中一个重要的概念:数据隐藏。
数据封装是一种将数据及其使用方法捆绑在一起的机制,而数据抽象则是一种仅公开接口、向用户隐藏实现细节的机制。
C++ 通过创建用户定义的类型(称为类)来支持封装和数据隐藏的特性。我们已经学习过,类可以包含私有、受保护和公共成员。默认情况下,类中定义的所有项都是私有的。例如:
class Box { public: double getVolume(void) { return length * breadth * height; } private: double length; // box 盒子的长度 double breadth; // box 盒子的宽度 double height; // box 盒子的高度 };
变量 length、breadth 和 height 是 private 的。这意味着它们只能被 Box 类的其他成员访问,而不能被程序的任何其他部分访问。这是实现封装的一种方式。
要使类的某些部分成为 public(即可以被程序的其他部分访问),必须在 public 关键字后声明它们。在 public 说明符后定义的所有变量或函数都可以被程序中的所有其他函数访问。
将一个类设为另一个类的友元会暴露实现细节,并降低封装性。理想情况下,每个类的细节应尽可能地对其他所有类隐藏。
数据封装示例
任何在 C++ 程序中实现具有 public 和 private 成员的类都是数据封装和数据抽象的示例。请考虑以下示例 -
#include <iostream> using namespace std; class Adder { public: // 构造函数 Adder(int i = 0) { total = i; } // 外部接口 void addNum(int number) { total += number; } // 外部接口 int getTotal() { return total; }; private: // 隐藏来自外界的数据 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: // 纯虚函数 virtual double getVolume() = 0; private: double length; // Box 的长度 double breadth; // Box 的宽度 double height; // Box 的高度 };
抽象类(通常称为 ABC)的目的是提供一个合适的基类,以便其他类可以继承。抽象类不能用于实例化对象,而只能用作接口。尝试实例化抽象类的对象会导致编译错误。
因此,如果需要实例化 ABC 的子类,它必须实现每个虚函数,这意味着它支持 ABC 声明的接口。如果在派生类中未重写纯虚函数,然后尝试实例化该类的对象,则会导致编译错误。
可用于实例化对象的类称为具体类。
抽象类示例
请考虑以下示例,其中父类为基类提供了一个接口,以实现名为 getArea() 的函数 -
#include <iostream> using namespace std; // 基类 class Shape { public: // 提供接口框架的纯虚函数。 virtual int getArea() = 0; void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 派生类 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); // 打印对象的面积。 cout << "Total Rectangle area: " << Rect.getArea() << endl; Tri.setWidth(5); Tri.setHeight(7); // 打印对象的面积。 cout << "Total Triangle area: " << Tri.getArea() << endl; return 0; }
当编译并执行上述代码时,它会产生以下结果 -
Total Rectangle area: 35 Total Triangle area: 17
您可以看到,一个抽象类如何根据 getArea() 定义一个接口,而另外两个类如何实现相同的函数,但使用不同的算法来计算特定形状的面积。
设计策略
面向对象系统可能会使用一个抽象基类来提供适用于所有外部应用程序的通用标准化接口。然后,通过从该抽象基类继承,可以形成具有类似操作的派生类。
外部应用程序提供的功能(即公共函数)在抽象基类中以纯虚函数的形式提供。这些纯虚函数的实现在与应用程序特定类型对应的派生类中提供。
即使在系统定义之后,这种架构也允许轻松地将新的应用程序添加到系统中。
C++ 文件和流
到目前为止,我们一直在使用 iostream 标准库,它提供了 cin 和 cout 方法,分别用于从标准输入读取和写入标准输出。
本教程将教您如何读取和写入文件。这需要另一个名为 fstream 的标准 C++ 库,它定义了三种新的数据类型 -
Sr.No | 数据类型 &说明 |
---|---|
1 | ofstream 此数据类型表示输出文件流,用于创建文件和将信息写入文件。 |
2 | ifstream 此数据类型表示输入文件流,用于从文件读取信息。 |
3 |
此数据类型通常表示文件流,并且同时具有 ofstream 和 ifstream 的功能,这意味着它可以创建文件、将信息写入文件以及读取来自文件的信息。 |
要在 C++ 中执行文件处理,必须在 C++ 源文件中包含头文件 <iostream> 和 <fstream>。
打开文件
必须先打开文件才能读取或写入文件。ofstream 或 fstream 对象可用于打开文件进行写入。而 ifstream 对象仅用于打开文件进行读取。
以下是 open() 函数 的标准语法,它是 fstream、ifstream 和 ofstream 对象的成员。
void open(const char *filename, ios::openmode mode);
这里,open() 成员函数的第一个参数指定要打开的文件的名称和位置,第二个参数定义打开文件的模式。
Sr.No | 模式标志和说明 |
---|---|
1 | ios::app 附加模式。所有输出到该文件的内容都将附加到末尾。 |
2 | ios::ate 打开一个文件进行输出,并将读/写控制权移至文件末尾。 |
3 | ios::in 打开一个文件进行读取。 |
4 | ios::out 打开一个文件进行写入。 |
5 | ios::trunc 如果文件已存在,则在打开文件之前会截断其内容。 |
您可以通过 或 操作将两个或多个值组合在一起。例如,如果您想以写入模式打开一个文件,并在文件已存在的情况下将其截断,则语法如下 -
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++ 编程时,您可以使用流插入运算符 (<<) 将信息从程序写入文件,就像使用该运算符将信息输出到屏幕一样。唯一的区别在于,您使用的是 ofstream 或 fstream 对象,而不是 cout 对象。
从文件读取
您可以使用流提取运算符 (>>) 将信息从文件读入程序,就像使用该运算符从键盘输入信息一样。唯一的区别在于,您使用的是 ifstream 或 fstream 对象,而不是 cin 对象。
读写示例
以下是一个以读写模式打开文件的 C++ 程序。在将用户输入的信息写入名为 afile.dat 的文件后,程序会从文件中读取信息并将其输出到屏幕上 -
#include <fstream> #include <iostream> using namespace std; int main () { char data[100]; // 以写入模式打开文件。 ofstream outfile; outfile.open("afile.dat"); cout << "Writing to the file" << endl; cout << "Enter your name: "; cin.getline(data, 100); // 将输入的数据写入文件。 outfile << data << endl; cout << "Enter your age: "; cin >> data; cin.ignore(); // 再次将输入的数据写入文件。 outfile << data << endl; //关闭打开的文件。 outfile.close(); // 以读取模式打开文件。 ifstream infile; infile.open("afile.dat"); cout << "Reading from the file" << endl; infile >> data; // 将数据写入屏幕。 cout << data << endl; // 再次从文件中读取数据并显示它。 infile >> data; cout << data << endl; //关闭打开的文件。 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() 函数用于忽略前一个 read 语句留下的多余字符。
文件位置指针
istream 和 ostream 都提供了用于重新定位文件位置指针的成员函数。这些成员函数分别是 istream 的 seekg("寻道获取")和 ostream 的 seekp("寻道放置")。
seekg 和 seekp 的参数通常为长整型。可以指定第二个参数来指示寻道方向。定位方向可以是 ios::beg(默认),用于相对于流的开头定位;ios::cur,用于相对于流中的当前位置定位;或者 ios::end,用于相对于流的结尾定位。
文件位置指针是一个整数值,它以距离文件起始位置的字节数来指定文件中的位置。以下是一些使用"get"文件位置指针定位的示例:-
// 定位到 fileObject 的第 n 个字节(假设使用 ios::beg) fileObject.seekg( n ); // 将 fileObject 向前移动 n 个字节 fileObject.seekg( n, ios::cur ); // 定位到 fileObject 末尾往后 n 个字节 fileObject.seekg( n, ios::end ); // 定位到 fileObject 末尾 fileObject.seekg( 0, ios::end );
C++ 异常处理
异常是程序执行过程中出现的问题。C++ 异常是对程序运行时出现的异常情况(例如尝试除以零)的响应。
异常提供了一种将控制权从程序的一部分转移到另一部分的方法。C++ 异常处理基于三个关键字:try、catch 和 throw。
throw − 当出现问题时,程序会抛出异常。这可以通过使用 throw 关键字来实现。
catch − 程序会在程序中需要处理问题的位置使用异常处理程序来捕获异常。 catch 关键字表示捕获异常。
try − try 块标识将触发特定异常的代码块。它后面跟着一个或多个 catch 块。
假设某个代码块会引发异常,则方法会使用 try 和 catch 关键字的组合来捕获异常。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 { // 受保护的代码 } catch( ExceptionName e ) { // 处理 ExceptionName 异常的代码 }
以上代码将捕获 ExceptionName 类型的异常。如果您想指定 catch 块处理 try 块中抛出的任何类型的异常,则必须在括号内放置一个省略号 (...),其中包含异常声明,如下所示 -
try { // 受保护的代码 } catch(...) { // 处理任何异常的代码 }
示例
以下示例抛出一个除以零的异常,我们在 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> 中定义的一系列标准异常,我们可以在程序中使用它们。这些异常按如下所示的父子类层次结构排列 -

以下是上述层次结构中提到的每个异常的简要描述 -
Sr.No | 异常 &描述 |
---|---|
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() 是异常类提供的公共方法,并且已被所有子异常类重写。该方法返回异常的原因。
C++ 动态内存
要成为一名优秀的 C++ 程序员,充分理解 C++ 中动态内存的工作原理至关重要。C++ 程序中的内存分为两部分:
栈 - 函数内声明的所有变量都会占用栈中的内存。
堆 - 这是程序中未使用的内存,可用于在程序运行时动态分配内存。
很多时候,您无法提前知道在定义的变量中存储特定信息需要多少内存,所需内存的大小可以在运行时确定。
您可以使用 C++ 中的特殊运算符在运行时在堆中为给定类型的变量分配内存,该运算符返回分配空间的地址。此运算符称为 new 运算符。
如果您不再需要动态分配的内存,可以使用 delete 运算符,它会取消分配先前由 new 运算符分配的内存。
new 和 delete 运算符
以下是使用 new 运算符为任何数据类型动态分配内存的通用语法。
new data-type;
这里的 data-type 可以是任何内置数据类型(包括数组)或任何用户定义的数据类型(包括类或结构体)。我们先从内置数据类型开始。例如,我们可以将指针定义为 double 类型,然后在执行时请求分配内存。我们可以使用 new 运算符来实现这一点,如下所示:-
double* pvalue = NULL; // 指针初始化为 null pvalue = new double; // 为变量申请内存
如果空闲存储空间已用完,则可能未成功分配内存。因此,最好检查 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; // 释放 pvalue 指向的内存
让我们将上述概念结合起来,并通过以下示例来展示 new 和 delete 的工作原理:-
#include <iostream> using namespace std; int main () { double* pvalue = NULL; // 指针初始化为 null pvalue = new double; // 为变量申请内存 *pvalue = 29494.99; // 将值存储在分配的地址 cout << "Value of pvalue : " << *pvalue << endl; delete pvalue; // 释放内存。 return 0; }
如果我们编译并运行上述代码,将产生以下结果 -
Value of pvalue : 29495
数组的动态内存分配
假设您要为一个字符数组(即包含 20 个字符的字符串)分配内存。使用与上面相同的语法,我们可以动态分配内存,如下所示。
char* pvalue = NULL; // 指针初始化为 null pvalue = new char[20]; // 为变量申请内存
要删除刚刚创建的数组,语句如下:-
delete [] pvalue; // 删除 pvalue 指向的数组
遵循与 new 运算符类似的通用语法,您可以按如下方式为多维数组分配内存 -
double** pvalue = NULL; // 指针初始化为 null pvalue = new double [3][4]; // 为 3x4 数组分配内存
但是,释放多维数组内存的语法仍然与上述相同 -
delete [] pvalue; // 删除 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; // 删除数组 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 { // 代码声明 }
要调用启用命名空间的函数或变量版本,请在前面添加 (::) 命名空间名称,如下所示 -
name::code; // 代码可以是变量或函数。
让我们看看命名空间如何作用于包括变量和函数在内的实体 -
#include <iostream> using namespace std; // 名字空间 namespace first_space { void func() { cout << "Inside first_space" << endl; } } // 第二个命名空间 namespace second_space { void func() { cout << "Inside second_space" << endl; } } int main () { // 从第一个命名空间调用函数。 first_space::func(); // 从第二个命名空间调用函数。 second_space::func(); return 0; }
如果我们编译并运行上述代码,将产生以下结果 -
Inside first_space Inside second_space
using 指令
您还可以使用 using namespace 指令避免在命名空间前添加前缀。该指令告知编译器后续代码正在使用指定命名空间中的名称。因此,以下代码隐含了该命名空间 -
#include <iostream> using namespace std; // 第一个命名空间 namespace first_space { void func() { cout << "Inside first_space" << endl; } } // 第二个命名空间 namespace second_space { void func() { cout << "Inside second_space" << endl; } } using namespace first_space; int main () { // 这从名字空间调用函数。 func(); return 0; }
如果我们编译并运行上述代码,将产生以下结果 -
Inside first_space
using 指令也可用于引用命名空间内的特定项。例如,如果您打算使用的 std 命名空间中唯一部分是 cout,则可以按如下方式引用它 -
using std::cout;
后续代码可以引用 cout 而无需在前面添加命名空间,但 std 命名空间中的其他项仍需要按如下方式显式指定 -
#include <iostream> using std::cout; int main () { cout << "std::endl is used with std!" << std::endl; return 0; }
如果我们编译并运行上述代码,将产生以下结果 -
std::endl is used with std!
在 using 指令中引入的名称遵循常规作用域规则。该名称从 using 指令处到该指令所在作用域的末尾均可见。在外部作用域中定义的同名实体将被隐藏。
不连续的命名空间
命名空间可以分为多个部分定义,因此命名空间由其单独定义的部分的总和组成。命名空间的各个部分可以分布在多个文件中。
因此,如果命名空间的某个部分需要另一个文件中定义的名称,则仍然必须声明该名称。编写以下命名空间定义可以定义新的命名空间或向现有命名空间添加新元素 −
namespace namespace_name { // 代码声明 }
嵌套命名空间
命名空间可以嵌套,您可以在一个命名空间内定义另一个命名空间,如下所示 -
namespace namespace_name1 { // 代码声明 namespace namespace_name2 { // 代码声明 } }
您可以使用解析运算符访问嵌套命名空间的成员,如下所示 -
// 访问 namespace_name2 的成员 using namespace namespace_name1::namespace_name2; // 访问 namespace:name1 的成员 using namespace namespace_name1;
如果在上述语句中使用了 namespace_name1,则 namespace_name2 中的元素将在作用域内可用,如下所示 -
#include <iostream> using namespace std; // 第一个名称空间 namespace first_space { void func() { cout << "Inside first_space" << endl; } // 第二个命名空间 namespace second_space { void func() { cout << "Inside second_space" << endl; } } } using namespace first_space::second_space; int main () { // 这会从第二个命名空间调用函数。 func(); return 0; }
如果我们编译并运行上述代码,将产生以下结果 -
Inside second_space
C++ 模板
模板是泛型编程的基础,泛型编程是一种编程风格,允许编写函数、类、算法以及处理不同数据类型的不同代码片段。
模板是创建泛型类或函数的蓝图或公式。迭代器和算法等库容器就是泛型编程的例子,它们都是使用模板概念开发的。
每个容器都有一个单一的定义,例如向量,但我们可以定义许多不同类型的向量,例如,vector
函数模板
函数模板定义了函数的蓝图,使函数能够操作不同的数据类型,而无需重写相同的逻辑。
语法
模板函数定义的一般形式的语法如下所示 -
template <typename identifier> function_declaration;
这里的"template"关键字声明了泛型函数,"typename"关键字指定了参数要使用的类型。
示例
以下是返回两个值中最大值的函数模板示例。
#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; // 元素 public: void push(T const&); // 推送元素 void pop(); // 弹出元素 T top() const; // 返回顶部元素 bool empty() const { // 如果为空,则返回 true。 return elems.empty(); } }; template <class T> void Stack<T>::push (T const& elem) { // 附加传递元素的副本 elems.push_back(elem); } template <class T> void Stack<T>::pop () { if (elems.empty()) { throw out_of_range("Stack<>::pop(): empty stack"); } // 删除最后一个元素 elems.pop_back(); } template <class T> T Stack<T>::top () const { if (elems.empty()) { throw out_of_range("Stack<>::top(): empty stack"); } // 返回最后一个元素的副本 return elems.back(); } int main() { try { Stack<int> intStack; // 整数堆栈 Stack<string> stringStack; // 字符串堆栈 // 操作 int 堆栈 intStack.push(7); cout << intStack.top() <<endl; // 操作字符串堆栈 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
模板参数推导
模板参数推导是一项功能,可以自动推导(理解)传递给函数或类模板的参数的数据类型。编译器无需显式指定模板参数,而是会为您计算出来。
示例
让我们看一个模板参数推导的示例 -
template<typename T> T add(T a, T b) { return a + b; } int main() { // 编译器将 T 推断为 int auto result1 = add(5, 3); // 编译器将 T 推断为 double auto result2 = add(3.14, 2.86); }
在这段代码中,我们没有写 add<int>(5,3) 或 add<double>(3.14, 2.86)。编译器会根据您提供的参数推断其类型。
函数模板参数推导
在 C++ 中,函数模板参数是一项功能,它允许编译器根据传递给函数的参数自动推断模板参数的类型。
示例
这是一个函数模板参数推导的简单示例。
#include <iostream> // 函数模板 template<typename T> void printValue(T value) { std::cout << value << std::endl; } int main() { // 使用示例 printValue(42); // T 为 int printValue("Hello"); // T 为 const char* printValue(3.14159); // T 为 double return 0; }
输出
42 Hello 3.14159
类模板参数推导
C++ 中的类模板参数推导功能使编译器能够在创建对象时根据构造函数参数自动推断类模板的模板参数。
示例
以下是类模板参数推导的基本实现。
#include <iostream> template<typename T> class Holder { public: Holder(T value) : data(value) {} void show() const { std::cout << data << std::endl; } private: T data; }; int main() { Holder h1(42); // T 推导为 int Holder h2(3.14); // T 推导为 double Holder h3("Hello"); // T 推导为 const char* h1.show(); // 输出:42 h2.show(); // 输出:3.14 h3.show(); // 输出:Hello return 0; }
输出
42 3.14 Hello
C++ 模板的优势
- 代码可重用性 − 使用模板,您可以编写适用于所有数据类型的通用代码,从而无需为每种所需类型编写相同的代码。通过减少代码重复,节省了开发时间。
- 减少维护 − 更新模板并查看所有实例的变化。这在修复错误、修复问题以及查看所有实例的优势方面效果更佳。
- 增强性能 − 模板实例化在编译时进行,从而减少运行时错误。编译器会针对特定数据类型优化代码。
- 更好地组织代码 − 由于模板将算法逻辑与数据类型分离,因此有助于创建模块化代码,这在开发场景中非常有利。它有助于减少对代码不同实现的搜索。
C++ 预处理器
预处理器指令是一些指令,它们指示编译器在实际编译开始之前对信息进行预处理。
所有预处理器指令都以 # 开头,并且一行中预处理器指令前只能出现空格。预处理器指令不是 C++ 语句,因此它们不以分号 (;) 结尾。
您已经在所有示例中看到了 #include 指令。此宏用于将头文件包含到源文件中。
C++ 支持许多预处理器指令,例如 #include、#define、#if、#else、#line 等。让我们看看一些重要的指令 -
#define 预处理器指令
#define 预处理器指令创建符号常量。这个符号常量被称为宏,其指令的一般形式为:-
#define macro-name 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; }
Function-Like Macros
You can use #define to define a macro which will take argument as follows −
#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; }
If we compile and run above code, this would produce the following result −
The minimum is 30
Conditional Compilation
There are several directives, which can be used to compile selective portions of your program's source code. This process is called conditional compilation.
The conditional preprocessor construct is much like the if selection structure. Consider the following preprocessor code −
#ifndef NULL #define NULL 0 #endif
You can compile a program for debugging purpose. You can also turn on or off the debugging using a single macro as follows −
#ifdef DEBUG cerr <<"Variable x = " << x << endl; #endif
This causes the cerr statement to be compiled in the program if the symbolic constant DEBUG has been defined before directive #ifdef DEBUG. You can use #if 0 statment to comment out a portion of the program as follows −
#if 0 code prevented from compiling #endif
Let us try the following example −
#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; }
If we compile and run above code, this would produce the following result −
The minimum is 30 Trace: Inside main function Trace: Coming out of main function
The # and ## Operators
The # and ## preprocessor operators are available in C++ and ANSI/ISO C. The # operator causes a replacement-text token to be converted to a string surrounded by quotes.
Consider the following macro definition −
#include <iostream> using namespace std; #define MKSTR( x ) #x int main () { cout << MKSTR(HELLO C++) << endl; return 0; }
If we compile and run above code, this would produce the following result −
HELLO C++
Let us see how it worked. It is simple to understand that the C++ preprocessor turns the line −
cout << MKSTR(HELLO C++) << endl;
Above line will be turned into the following line −
cout << "HELLO C++" << endl;
The ## operator is used to concatenate two tokens. Here is an example −
#define CONCAT( x, y ) x ## y
When CONCAT appears in the program, its arguments are concatenated and used to replace the macro. For example, CONCAT(HELLO, C++) is replaced by "HELLO C++" in the program as follows.
#include <iostream> using namespace std; #define concat(a, b) a ## b int main() { int xy = 100; cout << concat(x, y); return 0; }
If we compile and run above code, this would produce the following result −
100
Let us see how it worked. It is simple to understand that the C++ preprocessor transforms −
cout << concat(x, y);
Above line will be transformed into the following line −
cout << xy;
Predefined C++ Macros
C++ provides a number of predefined macros mentioned below −
Sr.No | Macro & Description |
---|---|
1 | __LINE__ This contains the current line number of the program when it is being compiled. |
2 | __FILE__ This contains the current file name of the program when it is being compiled. |
3 | __DATE__ This contains a string of the form month/day/year that is the date of the translation of the source file into object code. |
4 | __TIME__ This contains a string of the form hour:minute:second that is the time at which the program was compiled. |
Let us see an example for all the above macros −
#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; }
If we compile and run above code, this would produce the following result −
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++ 头文件 <csignal> 中。
Sr.No | 信号 &描述 |
---|---|
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."; // 清理并关闭此处的内容 // 终止程序 exit(signum); } int main () { // 注册信号SIGINT和信号处理程序 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."; // 清理并关闭此处的内容 // 终止程序 exit(signum); } int main () { int i = 0; // 注册信号SIGINT和信号处理程序 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)提供的 API 可在众多类 Unix POSIX 系统(例如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris)上使用。
创建线程
以下例程用于创建 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 连接例程等待线程完成。
#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; // 初始化并设置线程可连接 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); } } // 释放属性并等待其他线程 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 的简单架构 -

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 "; cout << "<html>"; cout << "<head>"; cout << "<title>Hello World - First CGI Program</title>"; cout << "</head>"; cout << "<body>"; cout << "<h2>Hello World! This is my first CGI program</h2>"; cout << "</body>"; cout << "</html>"; return 0; }
编译上述代码并将可执行文件命名为 cplusplus.cgi。该文件位于 /var/www/cgi-bin 目录中,内容如下。运行 CGI 程序之前,请确保已使用 UNIX 命令 chmod 755 cplusplus.cgi 更改文件模式,使文件变为可执行文件。
我的第一个 CGI 程序
上述 C++ 程序是一个简单的程序,它将输出写入 STDOUT 文件(即屏幕)。它有一个重要的额外功能,即在第一行打印 Content-type:text/html 。此行内容将返回浏览器,并指定要在浏览器屏幕上显示的内容类型。现在您一定已经理解了 CGI 的基本概念,可以使用 Python 编写许多复杂的 CGI 程序。 C++ CGI 程序可以与任何其他外部系统(例如 RDBMS)交互以交换信息。
HTTP 标头
Content-type:text/html 行是 HTTP 标头的一部分,它会发送给浏览器以理解其内容。所有 HTTP 标头均采用以下格式:
HTTP Field Name: Field Content For Example Content-type: text/html
还有一些其他重要的 HTTP 标头,您在 CGI 编程中会经常用到它们。
Sr.No | 标头和说明 |
---|---|
1 | Content-type: 定义返回文件格式的 MIME 字符串。例如,Content-type:text/html。 |
2 | Expires: Date 信息失效的日期。浏览器应使用此日期来决定何时需要刷新页面。有效的日期字符串应采用以下格式:1998 年 1 月 1 日 12:00:00 GMT。 |
3 | Location: URL 应返回的 URL,而不是请求的 URL。您可以使用此字段将请求重定向到任何文件。 |
4 | Last-modified: Date 资源的上次修改日期。 |
5 | Content-length: N 返回数据的长度(以字节为单位)。浏览器使用此值来报告文件的预计下载时间。 |
6 | Set-Cookie: String 设置通过字符串传递的 Cookie。 |
CGI 环境变量
所有 CGI 程序都可以访问以下环境变量。这些变量在编写任何 CGI 程序时都发挥着重要作用。
Sr.No | 变量名称 &说明 |
---|---|
1 | CONTENT_TYPE 内容的数据类型,客户端向服务器发送附加内容时使用。例如文件上传等。 |
2 | CONTENT_LENGTH 仅适用于 POST 请求的查询信息长度。 |
3 | HTTP_COOKIE 以 key & 的形式返回设置的 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 获取红外地址。 |
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 "; cout << "<html>"; cout << "<head>"; cout << "<title>CGI Environment Variables</title>"; cout << "</head>"; cout << "<body>"; cout << "<table border = \"0\" cellspacing = \"2\">"; for ( int i = 0; i < 24; i++ ) { cout << "<tr><td>" << ENV[ i ] << "</td><td>"; // 尝试检索环境变量的值 char *value = getenv( ENV[ i ].c_str() ); if ( value != 0 ) { cout << value; } else { cout << "Environment variable does not exist."; } cout << "</td></tr>"; } cout << "</table><"; cout << "</body>"; cout << "</html>"; 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 "; cout << "<html>"; cout << "<head>"; cout << "<title>Using GET and POST Methods</title>"; cout << "</head>"; cout << "<body>"; 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/>"; 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/>"; cout << "</body>"; cout << "</html>"; 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 "; cout << "<html>"; cout << "<head>"; cout << "<title>Checkbox Data to CGI</title>"; cout << "</head>"; cout << "<body>"; maths_flag = formData.queryCheckbox("maths"); if( maths_flag ) { cout << "Maths Flag: ON " << endl; } else { cout << "Maths Flag: OFF " << endl; } cout << "<br/>"; physics_flag = formData.queryCheckbox("physics"); if( physics_flag ) { cout << "Physics Flag: ON " << endl; } else { cout << "Physics Flag: OFF " << endl; } cout << "<br/>"; cout << "</body>"; cout << "</html>"; 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 "; cout << "<html>"; cout << "<head>"; cout << "<title>Radio Button Data to CGI</title>"; cout << "</head>"; cout << "<body>"; form_iterator fi = formData.getElement("subject"); if( !fi->isEmpty() && fi != (*formData).end()) { cout << "Radio box selected: " << **fi << endl; } cout << "<br/>"; cout << "</body>"; cout << "</html>"; return 0; }
将文本区域数据传递给 CGI 程序
当需要将多行文本传递给 CGI 程序时,使用 TEXTAREA 元素。
以下是包含 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 "; cout << "<html>"; cout << "<head>"; cout << "<title>Text Area Data to CGI</title>"; cout << "</head>"; cout << "<body>"; 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/>"; cout << "</body>"; cout << "</html>"; 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 "; cout << "<html>"; cout << "<head>"; cout << "<title>Drop Down Box Data to CGI</title>"; cout << "</head>"; cout << "<body>"; form_iterator fi = formData.getElement("dropdown"); if( !fi->isEmpty() && fi != (*formData).end()) { cout << "Value Selected: " << **fi << endl; } cout << "<br/>"; cout << "</body>"; cout << "</html>"; return 0; }
在 CGI 中使用 Cookie
HTTP 协议是一种无状态协议。但对于商业网站来说,它需要在不同页面之间维护会话信息。例如,一个用户在完成多个页面后注册就结束了。但如何在所有网页上维护用户的会话信息呢?
在许多情况下,使用 Cookie 是记住和跟踪偏好、购买记录、佣金以及其他改善访客体验或网站统计信息所需的信息的最有效方法。
工作原理
您的服务器会以 Cookie 的形式向访问者的浏览器发送一些数据。浏览器可能会接受 Cookie。如果接受,它会以纯文本记录的形式存储在访问者的硬盘上。现在,当访问者访问您网站上的另一个页面时,就可以检索该 Cookie 了。一旦检索到,您的服务器就会知道/记住存储的内容。
Cookie 是由 5 个可变长度字段组成的纯文本数据记录 -
Expires - 这表示 Cookie 的过期日期。如果此字段为空,则 Cookie 将在访问者退出浏览器时过期。
Domain - 这表示您网站的域名。
Path - 这表示设置 Cookie 的目录或网页的路径。如果您想从任何目录或页面检索 Cookie,则可以为空。
Secure - 如果此字段包含"secure"一词,则只能使用安全的服务器检索 Cookie。如果此字段为空,则不存在此限制。
Name = Value − Cookie 以键值对的形式设置和检索。
设置 Cookie
向浏览器发送 Cookie 非常简单。这些 Cookie 会随 HTTP 标头一起发送到 Content-type 字段之前。假设您想将 UserID 和 Password 设置为 Cookie。那么 Cookie 设置步骤如下
#include <iostream> using namespace std; int main () { cout << "Set-Cookie:UserID = XYZ;"; cout << "Set-Cookie:Password = XYZ123;"; cout << "Set-Cookie:Domain = www.tutorialspoint.com;"; cout << "Set-Cookie:Path = /perl;"; cout << "Content-type:text/html "; cout << "<html>"; cout << "<head>"; cout << "<title>Cookies in CGI</title>"; cout << "</head>"; cout << "<body>"; cout << "Setting cookies" << endl; cout << "<br/>"; cout << "</body>"; cout << "</html>"; return 0; }
从这个例子中,您一定已经了解了如何设置 Cookie。我们使用 Set-Cookie HTTP 标头来设置 Cookie。
在这里,您可以选择设置 Cookie 属性,例如 Expires、Domain 和 Path。值得注意的是,cookie 是在发送魔法行 "Content-type:text/html 之前设置的。
编译上述程序生成 setcookies.cgi 文件,并尝试使用以下链接设置 cookie。它将在您的计算机上设置四个 cookie -
检索 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 "; cout << "<html>"; cout << "<head>"; cout << "<title>Cookies in CGI</title>"; cout << "</head>"; cout << "<body>"; 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>"; } cout << "</table><"; cout << "<br/>"; cout << "</body>"; cout << "</html>"; return 0; }
现在,编译上述程序生成 getcookies.cgi 文件,并尝试获取计算机上所有可用 Cookie 的列表 -
这将生成上一节中设置的所有四个 Cookie 以及计算机上设置的所有其他 Cookie 的列表 -
UserID XYZ Password XYZ123 Domain www.tutorialspoint.com Path /perl
文件上传示例
要上传文件,HTML 表单必须将 enctype 属性设置为 multipart/form-data。带有文件类型的输入标签将创建一个"浏览"按钮。
<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 "; cout << "<html>"; cout << "<head>"; cout << "<title>File Upload in CGI</title>"; cout << "</head>"; cout << "<body>"; // 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>"; cout << "</body>"; cout << "</html>"; return 0; }
以上示例是在 cout 流中写入内容,但您可以打开文件流并将上传文件的内容保存到所需位置的文件中。
希望您喜欢本教程。如果喜欢,请向我们发送您的反馈。