CPP Note
hello.cpp -> 编译代码g++ hello.cpp -o a
-> a.out
区分大小写的编程语言
内置类型
一些基本类型可以使用一个或多个类型修饰符进行修饰:
- signed: char, int
- unsigned: char, int
- short: int
- long: int, double
类型 | 字节 |
---|---|
bool | |
char | 1 |
int | 4, 2(short), 8(long) |
float | 4 |
double | 8 |
void | |
wchar_t(宽字符型) | 2, 4 |
typedef 声明
使用 typedef
为一个已有的类型取一个新的名字:
1 | typedef int feet; |
枚举类型
1 | enum enum-name { list of names } var-list; |
1 | enum color { red, green, blue } c; |
1 | enum color { red, green=5, blue }; |
则 blue = 6
变量定义
1 | type variable_list; |
变量声明
变量声明向编译器保证变量以给定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。
全局变量默认值
类型 | 默认值 |
---|---|
int | 0 |
char | ‘\0’ |
float | 0 |
double | 0 |
pointer | NULL |
常量
0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。
整数常量
也可以带一个或者多个后缀:
- ‘U‘: unsigned
- ‘L‘: long
1 | 85 // 十进制 |
浮点常量
由整数部分、小数点、小数部分和指数部分组成
1 | 3.14159 // 合法的 |
布尔常量
- true: 值代表真。
- false: 值代表假。
不应把 true 的值看成 1,把 false 的值看成 0。
字符常量
字符常量是括在单引号中。如果常量以 L(仅当大写时)开头,则表示它是一个宽字符常量(例如 L’x’),此时它必须存储在 wchar_t 类型的变量中。否则,它就是一个窄字符常量(例如 ‘x’),此时它可以存储在 char 类型的简单变量中。
转义序列 | 含义 |
---|---|
\ | \ 字符 |
\’ | ‘ 字符 |
\” | “ 字符 |
\? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo|一到三位的八进制数|
\xhh . . .|一个或多个数字的十六进制数|
字符串常量
下面这三种形式所显示的字符串是相同的。
1 | "hello, dear" |
定义常量
两种简单的定义常量的方式:
- 使用 #define 预处理器。
- 使用 const 关键字。
#define 预处理器
1 | #define identifier value |
1 | #define LENGTH 10 |
const 关键字
1 | const type variable = value; |
1 | int main() { |
修饰符类型
可以不写 int,只写单词 unsigned、short 或 unsigned、long,int 是隐含的
1 | unsigned x; |
类型限定符
限定符 | 含义 |
---|---|
const | const 类型的对象在程序执行期间不能被修改改变。 |
volatile | 修饰符 volatile 告诉编译器,变量的值可能以程序未明确指定的方式被改变。 |
restrict | 由 restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 |
存储类
存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。
- auto: 从 C++ 11 开始不再是 C++ 存储类说明符
- register: 从 C++ 11 开始被弃用
- static
- extern
- mutable
- thread_local (C++11)
auto 存储类
声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。使用极少且多余,在C++11中已删除这一用法。
register 存储类
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。
寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
static 存储类
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。
static 可用于局部变量和全局变量
extern 存储类
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。
通过 extern 声明其它文件的变量,然后在本文件中使用。
mutable 存储类
mutable 说明符仅适用于类的对象,它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。
thread_local 存储类
使用 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。 |
函数参数
调用类型 | 描述 |
---|---|
传值调用 | 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。 |
指针调用 | 该方法把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
引用调用 | 该方法把参数的引用复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。 |
默认情况下,C++ 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的参数。之前提到的实例,调用 max() 函数时,使用了相同的方法。
参数的默认值
1 | int sum(int a, int b=20) |
Lambda 函数与表达式
1 | // 有参数:[capture](parameters)->return-type{body} |
变量传递有传值和传引用的区别。可以通过前面的[]来指定:
1 | [] // 沒有定义任何变量。使用未定义变量会引发错误。 |
对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入:
1 | [this]() { this->someFunc(); }(); |
数学运算
引用数学头文件 \
sin, cos, tan, log, pow, hypot(两数总和平方根), fabs(绝对值)
随机数
1 | // 设置种子 |
数组
声明数组
数组是一个常量指针
1 | // type arrayName [ arraySize ]; |
初始化数组
1 | double balance[5] = {1000.0, 2.0, 3.4, 17.0, 50.0}; |
直接初始化 char 数组是特殊的, 这种初始化要记得字符是以一个 null 结尾的。
1 | char a1[] = {'C', '+', '+'}; // 初始化,没有 null |
指针变量声明
1 | type *var-name; |
1 | int var = 20; |
NULL 指针
NULL 指针是一个定义在标准库中的值为零的常量:
1 | int *nullP = NULL; |
指针的算术运算
1 | ptr++ |
假设 ptr 是一个指向地址 1000 的整型指针,是一个 32 位的整数(4个字节),,ptr 将指向位置 1004,因为 ptr 每增加一次,它都将指向下一个整数位置,即当前位置往后移 4 个字节。
如果 ptr 指向一个地址为 1000 的字符(1个字节),上面的运算会导致指针指向位置 1001,因为下一个字符位置是在 1001。
数组是一个常量指针
传递指针给函数
1 | int *pr; |
从函数返回指针
C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
1 | int var = 20; |
引用 vs 指针
引用很容易与指针混淆,它们之间有三个主要的不同:
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
创建引用
1 | // 声明简单的变量 |
在这些声明中,& 读作引用。
1 | int swapA = 1; |
1 | int swapA = 1; |
把引用作为返回值
当函数返回一个引用时,则返回一个指向返回值的隐式指针。这样,函数就可以放在赋值语句的左边。
1 | double vals[] = {10.1, 12.6, 33.1, 24.1, 50.0}; |
返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
日期 & 时间
C/C++ 标准库, 引用 \
有四个与时间相关的类型:clock_t、time_t、size_t 和 tm
构类型 tm 把日期和时间以 C 结构的形式保存
1 | struct tm { |
1 | time_t now = time(0); // 基于当前系统的当前日期/时间 |
标准输出流(cout)
预定义的对象 cout
是 ostream
类的一个实例, 流插入运算符 <<
1 | char str[] = "Hello C++"; |
标准输入流(cin)
预定义的对象 cin 是 istream 类的一个实例。流提取运算符 >>
1 | char name[50]; |
标准错误流(cerr)
1 | char str[] = "Fake Error..."; |
标准日志流(clog)
1 | char strLog[] = "Fake Log..."; |
数据结构
定义结构
使用 struct 语句。struct 语句定义了一个包含多个成员的新的数据类型
1 | struct type_name { |
type_name 是结构体类型的名称,member_type1 member_name1 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在结构定义的末尾,最后一个分号之前,您可以指定一个或多个结构变量,这是可选的。
1 | struct Books |
访问结构成员
使用成员访问运算符(.)
1 | struct Book{ |
结构作为函数参数
1 | void printBook(struct Book book){ |
指向结构的指针
1 | Book *book1P = &book1; |
为了使用指向该结构的指针访问结构的成员,您必须使用 ->
运算符
typedef 关键字
定义结构的方式,可以为创建的类型取一个”别名”:
1 | typedef struct |
可以使用 typedef 关键字来定义非结构类型:
1 | typedef long int *pint32; |
x, y 和 z 都是指向长整型 long int 的指针。
类和对象
创建类
1 | class Box { |
成员函数
范围解析运算符 ::
1 | class Box { |
类访问修饰符
public
, protected
, private
(默认)
1 | class Base { |
public
成员在程序中类的外部是可访问的.
private
成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
protected
成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。
类的构造函数
1 | Box::Box(double length, double breadth, double height){ |
类的析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
1 | ~Box(); |
拷贝构造函数
http://www.runoob.com/cplusplus/cpp-copy-constructor.html
1 | classname (const classname &obj) { |
1 | Line( const Line &obj); // 拷贝构造函数 |
友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
friend关键字
声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置如下声明:friend class ClassTwo;
1 | class Box |
内联函数
内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
1 | inline int Max(int x, int y) |
this 指针
每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
指向类的指针
一个指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化。
1 | Box *boxBPr = &boxB; |
类的静态成员
1 | static int objectCount; |
1 | static bool staticFunc(); |
基类 & 派生类
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:
class derived-class: access-specifier base-class
access-specifier
是 public
、protected
或 private
其中的一个,默认为 private
,base-class
是之前定义过的某个类的名称。
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
继承类型
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
多继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
语法如下:
1 | class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,… |
函数重载
当您调用一个重载函数或重载运算符时,编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程,称为重载决策。
1 | void Rectangle::print(int intValue) { |
运算符重载
可以重定义或重载大部分 C++ 内置的运算符。这样,您就能使用自定义类型的运算符。
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
1 | Box operator+(const Box&); |
声明加法运算符用于把两个 Box 对象相加,返回最终的 Box 对象。
大多数的重载运算符可被定义为普通的非成员函数或者被定义为类成员函数。
1 | Box operator+(const Box&, const Box&); |
可重载运算符
+ | - | * | / | % | ^ | ||
& | \ | ~ | ! | , | = | ||
< | > | <= | >= | ++ | – | ||
<< | >> | == | != | && | \ | \ | |
+= | -= | /= | %= | ^= | &= | ||
\ | = | *= | <<= | >>= | [] | () | |
-> | ->* | new | new [] | delete | delete [] |
不可重载运算符
:: | .* | . | ?: |
运算符重载实例: http://www.runoob.com/cplusplus/cpp-overloading.html
多态
1 | // in Shape class |
1 | // out put |
调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。
放置关键字virtual后,编译器看的是指针的内容,而不是它的类型。
虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
纯虚函数
您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
1 | // pure virtual function |
= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
文件和流
数据类型 | 描述 |
---|---|
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息。 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息。 |
fstream | 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。 |
模式标志 | 描述 |
---|---|
ios::app | 追加模式。所有写入都追加到文件末尾。 |
ios::ate | 文件打开后定位到文件末尾。 |
ios::in | 打开文件用于读取。 |
ios::out | 打开文件用于写入。 |
ios::trunc | 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 0。 |
1 | const char *filePath = "/Users/wangjie/work/cpp/HelloCpp/com.wangjie.cpptest/file_temp"; |
异常处理
抛出异常:
1 | double division(int a, int b) |
C++ 提供了一系列标准的异常,定义在
定义新的异常:
异常 | 描述 |
---|---|
std::exception | 该异常是所有标准 C++ 异常的父类。 |
std::bad_alloc | 该异常可以通过 new 抛出。 |
std::bad_cast | 该异常可以通过 dynamic_cast 抛出。 |
std::bad_exception | 这在处理 C++ 程序中无法预期的异常时非常有用。 |
std::bad_typeid | 该异常可以通过 typeid 抛出。 |
std::logic_error | 理论上可以通过读取代码来检测到的异常。 |
std::domain_error | 当使用了一个无效的数学域时,会抛出该异常。 |
std::invalid_argument | 当使用了无效的参数时,会抛出该异常。 |
std::length_error | 当创建了太长的 std::string 时,会抛出该异常。 |
std::out_of_range | 该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator[]()。 |
std::runtime_error | 理论上不可以通过读取代码来检测到的异常。 |
std::overflow_error | 当发生数学上溢时,会抛出该异常。 |
std::range_error | 当尝试存储超出范围的值时,会抛出该异常。 |
std::underflow_error | 当发生数学下溢时,会抛出该异常。 |
1 |
|
动态内存
内存分为两个部分:
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
很多时候,您无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。
在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。
如果您不需要动态分配内存,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。
1 | double* pvalue = NULL; // 初始化为 null 的指针 |
命名空间
定义命名空间:
1 | namespace namespace_name { |
1 | namespace first_space { |
可以使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。
using 指令也可以用来指定命名空间中的特定项目。例如,如果您只打算使用 std 命名空间中的 cout 部分,您可以使用如下的语句:
1 | using std::cout; |
不连续的命名空间
命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。一个命名空间的各个组成部分可以分散在多个文件中。
下面的命名空间定义可以是定义一个新的命名空间,也可以是为已有的命名空间增加新的元素:1
2
3namespace namespace_name {
// 代码声明
}
函数模板
1 | template <typename T> |
类模板
1 | template <class type> class class-name { |
预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。
我们已经看到,之前所有的实例中都有 #include 指令。这个宏用于把头文件包含到源文件中。
C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令。
#define 预处理
#define 预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:
1 | #define macro-name replacement-text |
1 |
|
函数宏:
1 |
|
条件编译: http://www.runoob.com/cplusplus/cpp-preprocessor.html
信号处理
信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。
有些信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件
信号 | 描述 |
---|---|
SIGABRT | 程序的异常终止,如调用 abort。 |
SIGFPE | 错误的算术运算,比如除以零或导致溢出的操作。 |
SIGILL | 检测非法指令。 |
SIGINT | 接收到交互注意信号。 |
SIGSEGV | 非法访问内存。 |
SIGTERM | 发送到程序的终止请求。 |
signal() 函数
用来捕获突发事件。以下是 signal() 函数的语法:
1 | void (*signal (int sig, void (*func)(int)))(int); |
这个函数接收两个参数:第一个参数是一个整数,代表了信号的编号;第二个参数是一个指向信号处理函数的指针。
1 | void signalHandler( int signum ){ |
1 | Going to sleep.... |
raise() 函数
可以使用函数 raise() 生成信号,该函数带有一个整数信号编号作为参数,语法如下:
1 | int raise (signal sig); |
在这里,sig 是要发送的信号的编号,这些信号包括:SIGINT、SIGABRT、SIGFPE、SIGILL、SIGSEGV、SIGTERM、SIGHUP。以下是我们使用 raise() 函数内部生成信号的实例:
1 | void signalHandler( int signum ){ |
1 | Going to sleep.... |