基本的内置类型
布尔型 | bool |
字符型 | char |
整型 | int |
浮点型 | float |
双浮点型 | double |
无类型 | void |
宽字符型 | wchar_t |
变量声明
type variable_list;
变量声明
使用多个文件且只在其中一个文件中定义变量时(定义变量的文件在程序连接时是可用的),变量声明就显得非常有用。可以使用 extern 关键字在任何地方声明一个变量。
可以在 C++ 程序中多次声明一个变量,但变量只能在某个文件、函数或代码块中被定义一次。
作用域
全局作用域、局部作用域
定义常量
在 C++ 中,有两种简单的定义常量的方式:
· 使用 #define 预处理器。
· 使用 const 关键字。
#define identifier value
const type variable = value;
左值(Lvalues)和右值(Rvalues)
· 左值(lvalue):指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。
· 右值(rvalue):术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。
修饰符类型
· signed
· unsigned
· long
· short
类型限定符
const | const 类型的对象在程序执行期间不能被修改改变。 |
volatile | 修饰符 volatile 告诉编译器,变量的值可能以程序未明确指定的方式被改变。 |
restrict | 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 |
存储类
存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。
· auto
· register
· static
· extern
· mutable
· thread_local (C++11)
从 C++ 11 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用。
auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
register 修饰符用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。
寄存器只用于需要快速访问的变量,比如计数器。
static 修饰符指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
extern 修饰符用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 'extern' 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
mutable 说明符仅适用于类的对象,这将在本教程的最后进行讲解。它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。变量在创建线程时创建,并在销毁线程时销毁。每个线程都有其自己的变量副本。
thread_local 说明符可以与 static 或 extern 合并。
thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
运算符
· 算术运算符
· 关系运算符
· 逻辑运算符
· 位运算符
· 赋值运算符
· 杂项运算符
sizeof | sizeof 运算符返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。 |
Condition ? X : Y | 条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。 |
, | 逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。 |
.(点)和 ->(箭头) | 成员运算符用于引用类、结构和共用体的成员。 |
Cast | 强制转换运算符把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。 |
& | 指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。 |
* | 指针运算符 * 返回一个变量的值。例如,*var; 将指向变量 var。 |
&将变量变为指针 *将指针变为变量值
循环
当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。 | |
多次执行一个语句序列,简化管理循环变量的代码。 | |
除了它是在循环主体结尾测试条件外,其他与 while 语句类似。 | |
您可以在 while、for 或 do..while 循环内使用一个或多个循环。 |
终止 loop 或 switch 语句,终止循环。 | |
跳出当前循环,进行下一项循环。 | |
将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。 |
判断语句
其他语言一样
函数
每个 C++ 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。
在调用函数的文件顶部声明函数!!
函数参数
该方法把参数的实际值复制给函数的形式参数(传入变量值)。在这种情况下,修改函数内的形式参数对实际参数没有影响。 | |
该方法把参数的地址复制给形式参数(传入变量地址)。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 | |
该方法把参数的引用复制给形式参数(声明函数参数为引用)。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
多维数组
int a[3][4] = {
{0, 1, 2, 3} , /* 初始化索引号为 0 的行 */
{4, 5, 6, 7} , /* 初始化索引号为 1 的行 */
{8, 9, 10, 11} /* 初始化索引号为 2 的行 */
}; //内部嵌套的括号是可选的
字符串
· C 风格字符串
· C++ 引入的 string 类类型
char greeting[] = "Hello";
#include <string>
string str1 = "Hello";
指针
指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。
在使用指针存储其他变量地址之前,应对其进行声明。
NULL指针
C++ 支持空指针。NULL(nullptr) 指针是一个定义在标准库中的值为零的常量。
引用
· 不存在空引用。引用必须连接到一块合法的内存。
· 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
· 引用必须在创建时被初始化。指针可以在任何时间被初始化。
int i = 17;
int& r = i;
日期和时间
#include <ctime>
为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用 <ctime> 头文件。
有四个与时间相关的类型:clock_t、time_t、size_t 和 tm。类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数。
I/O 库头文件
<iostream> | 该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。 |
<iomanip> | 该文件通过所谓的参数化的流操纵器(比如 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务。 |
<fstream> | 该文件为用户控制的文件处理声明服务。我们将在文件和流的相关章节讨论它的细节。 |
预定义的对象 cout 是 ostream 类的一个实例。cout 对象"连接"到标准输出设备,通常是显示屏。cout 是与流插入运算符 << 结合使用的,如下所示:
预定义的对象 cin 是 istream 类的一个实例。cin 对象附属到标准输入设备,通常是键盘。cin 是与流提取运算符 >> 结合使用的,如下所示:
预定义的对象 cerr 是 ostream 类的一个实例。cerr 对象附属到标准错误设备,通常也是显示屏,但是 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。
预定义的对象 clog 是 ostream 类的一个实例。clog 对象附属到标准错误设备,通常也是显示屏,但是 clog 对象是缓冲的。这意味着每个流插入到 clog 都会先存储在缓冲区,直到缓冲填满或者缓冲区刷新时才会输出。
数据结构
结构( struct )是 C++ 中另一种用户自定义的可用的数据类型,它允许您存储不同类型的数据项。
指向结构的指针
您可以定义指向结构的指针,方式与定义指向其他类型变量的指针相似,如下所示:
struct Books *struct_pointer;
C++类
类访问修饰符
public、private、protected
Box* Box1 = new Box(); // 使用堆
Box Box2; // 使用栈
a->b 的含義是 (*a).b
1. 堆和栈最大可分配的内存的大小
2. 堆和栈的内存管理方式
3. 堆和栈的分配效率
· 一般来说对于一个进程栈的大小远远小于堆的大小, 所以当需要一个分配的大小的内存时,请用new,即用堆。
· 栈是系统数据结构,对于进程/线程是唯一的,它的分配与释放由操作系统来维护,不需要开发者来管理。
· 栈内存分配运算内置于处理器的指令集中,效率很高,不同的操作系统对栈都有一定的限制。
· 我们需要的内存很少,又能确定你到底需要多少内存时,请用栈。而当需要在运行时才知道你到底需要多少内存时,请用堆。
· 堆的效率比栈要低得多
类的析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员
尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend
内联函数inline
在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符
引入内联函数的目的是为了解决程序中函数调用的效率问题
this指针
每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
指向类的指针
一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。
继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。
· 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问
· 保护继承(protected):当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员
· 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员
多继承
C++ 类可以从多个类继承成员
class <派生类名>:<继承类型><基类名1>,<继承类型><基类名2>,…
{
<派生类类体>
};
重载运算符和重载函数
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
重载运算符
您可以重定义或重载大部分 C++ 内置的运算符。
其他函数一样,重载运算符有一个返回类型和一个参数列表。
// 重载 + 运算符,用于把两个 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;
};
http://www.runoob.com/cplusplus/cpp-overloading.html可重载运算符/不可重载运算符
多态
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。和其他语言中的多态一样
纯虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
virtual int area() = 0;
相当于java中接口,没有主体
数据抽象
只暴露特定接口,通过get set操作
抽象类(接口)
对方法使用virtual关键字
文件和流
标准库 fstream
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息。 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息。 |
fstream | 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。 |
要在 C++ 中进行文件处理,必须在 C++ 源代码文件中包含头文件 <iostream> 和 <fstream>
ofstream 和 fstream 对象都可以用来打开文件进行写操作,如果只需要打开文件进行读操作,则使用 ifstream 对象。
void open(const char *filename, ios::openmode mode);
open() 成员函数第一参数指定要打开的文件的名称和位置,第二个参数定义文件被打开的模式
ios::app | 追加模式。所有写入都追加到文件末尾。 |
ios::ate | 文件打开后定位到文件末尾。 |
ios::in | 打开文件用于读取。 |
ios::out | 打开文件用于写入。 |
ios::trunc | 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。 |
写入文件
在 C++ 编程中,我们使用流插入运算符( << )向文件写入信息,就像使用该运算符输出信息到屏幕上一样。唯一不同的是,在这里您使用的是 ofstream 或 fstream 对象,而不是 cout 对象。
读取文件
在 C++ 编程中,我们使用流提取运算符( >> )从文件读取信息,就像使用该运算符从键盘输入信息一样。唯一不同的是,在这里您使用的是 ifstream 或 fstream 对象,而不是 cin 对象。
使用结束后调用close()方法
异常处理
C++ 异常处理涉及到三个关键字:try、catch、throw
· throw: 当问题出现时,程序会抛出一个异常。这是通过使用 throw 关键字来完成的
· catch: 在您想要处理问题的地方,通过异常处理程序捕获异常。catch 关键字用于捕获异常
· try: try 块中的代码标识将被激活的特定异常。它后面通常跟着一个或多个 catch 块
C++ 提供了一系列标准的异常
http://www.runoob.com/cplusplus/cpp-exceptions-handling.html
throw包含在try的执行代码中,如果遇到异常执行throw操作传入catch参数,在catch执行异常处理。
动态内存
C++ 程序中的内存分为两个部分:
· 栈:在函数内部声明的所有变量都将占用栈内存。
· 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。
使用new创建的对象分配堆内的内存,非new创建的对象使用栈内存。
如果您不需要动态分配内存,使用 delete 运算符,删除之前由 new 运算符分配的内存。
如果自由存储区已被用完,可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针,并采取以下适当的操作:
double* pvalue = NULL;
if (!(pvalue = new double)) {
cout << "Error: out of memory." << endl;
exit(1);
}
malloc() 函数在 C 语言中就出现了,在 C++ 中仍然存在,但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。
数组的动态内存分配
假设我们要为一个字符数组(一个有 20 个字符的字符串)分配内存,我们可以使用上面实例中的语法来为数组动态地分配内存,如下所示:
char* pvalue = NULL; // 初始化为 null 的指针
pvalue = new char [20]; // 为变量请求内存
delete[] pvalue; // 删除 pvalue 所指向的数组
多维数组使用指针的指针(多个星号),一层一层地分配内存,释放内存时也要循环进行释放。
(数组创建返回的是其内存地址)
对象的动态内存分配
对象与简单的数据类型没有什么不同,为一个包含四个Box对象的数组分配内存:
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // 删除数组
命名空间namespace
namespace namespace_name {
// 代码声明
void func() {
//执行语句
}
}
在命名空间namespace_name中定义了一个函数func(),使用时通过namespace_name::func()调用
using指令
using namespace space_name; //命名空间中的所有项目都不需要加前缀
using space_name::specified; //命名空间中指定的项目不需要加前缀
命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间
函数模板(泛型)
模板函数定义的一般形式如下所示:
template <class type>
ret-type func-name (parameter list) {
// 函数的主体
}
func-name<SpecificType>(SpecificParams)
类模板(泛型)
template <class type>
class class-name {
T func(T p) {
return p;
};
}
class-name<SpecificType> name;
预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。
宏定义是直接替换,不会分配内存,存储与程序的代码段中;
#include 指令,这个宏用于把头文件包含到源文件中
#define 预处理
#define 预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:
#define macro-name replacement-text
#define PI 3.14159
可以使用 #define 来定义一个带有参数的宏
#define MIN(a,b) (a<b ? a : b)
条件编译
有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译(条件预处理器)。
#ifndef NULL //判断宏NULL是否定义,定义了则执行内容
#define NULL 0
#endif
如果在指令 #ifdef DEBUG 之前已经定义了符号常量 DEBUG,则会对程序中的 cerr 语句进行编译。可以使用 #if 0 语句注释掉程序的一部分,如下所示:
#if 0 //条件为true则执行内容
不进行编译的代码
#endif
#和##运算符
# 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。
#define MKSTR(x) #x
MKSTR使用MKSTR(hello)会变成 hello 字符串
#define CONCAT( x, y ) x ## y //## 运算符用于连接两个令牌
如果x和y不是字符串,拼接后返回变量名。如果是字符串则返回字符串。
C++ 中的预定义宏
__LINE__ | 这会在程序编译时包含当前行号。 |
__FILE__ | 这会在程序编译时包含当前文件名。 |
__DATE__ | 这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。 |
__TIME__ | 这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。 |
预处理器
信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。
有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件 <csignal> 中。
SIGABRT | 程序的异常终止,如调用 abort。 |
SIGFPE | 错误的算术运算,比如除以零或导致溢出的操作。 |
SIGILL | 检测非法指令。 |
SIGINT | 接收到交互注意信号。 |
SIGSEGV | 非法访问内存。 |
SIGTERM | 发送到程序的终止请求。 |
signal()函数
C++ 信号处理库提供了 signal 函数,用来捕获突发事件。以下是 signal() 函数的语法:
void (*signal (int sig, void (*func)(int)))(int);
// 注册信号 SIGINT 和信号处理程序
signal(SIGINT, signalHandler);
raise()函数
可以使用函数 raise() 生成信号,该函数带有一个整数信号编号作为参数,语法如下:
int raise (signal sig); //手动发出信号
在这里,sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。
多线程
多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下,两种类型的多任务处理:基于进程和基于线程。
· 基于进程的多任务处理是程序的并发执行。
· 基于线程的多任务处理是同一程序的片段的并发执行。
那些可以与其他计算并行执行的计算为任务(task),线程(thread)是任务在系统级表示。
开启线程通过引入<thread>构造std::thread并将任务作为它的实参
thread t1 {f}; //f()在独立的线程中执行
t1.join();
join()保证在线程完成后才退出当前作用域,join的意思是“等待线程结束”。