(一)基础知识
1. 发展历史
- C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。
- C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点。
- C++ 是由 Bjarne Stroustrup 于 1979 年在新泽西州美利山贝尔实验室开始设计开发的。C++ 进一步扩充和完善了 C 语言,最初命名为带类的C,后来在 1983 年更名为 C++。
- C++ 是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序。
2. 变量
2.1 数据类型
- 在c++中,变量保存的是它所存储的值的内存地置。当您创建一个变量时,就会在内存中预留一些空间。
- 空间预留多少合适,这就由变量的数据类型所决定。
- 在C++中数据类型不仅可以预留空间,还可以决定内存中存什么。
- 在C++中数据类型包括:char、int、float、double、bool、void等。
2.1.1 基本的数据类型
C++ 为程序员提供了种类丰富的内置数据类型和用户自定义的数据类型。下表列出了七种基本的 C++ 数据类型:
类型 | 关键字 |
---|---|
布尔型 | bool |
字符型 | char |
整型 | int |
浮点型 | float |
双浮点型 | double |
无类型 | void |
宽字符型 | wchar_t |
2.2 修饰符
在C++中,char、int和double前面可以添加修饰符,改变原来类型的含义,来适应更多情景的需求。
下面列出了数据类型修饰符:
- signed
- unsigned
- long
- short
其中,signed、unsigned、long和short用来修饰整型,signed和unsigned用于修饰字符,long用来修饰双精度型。
2.3 字符串
C++延续了 C 的字符串风格,用 null 字符 \0 终止的一维字符数组。
char a[6] = {'H', 'E', 'L', 'L', 'O', '\0'};
下面的声明和初始化创建了一个 HELLO 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 HELLO 的字符数多一个。而在打印时不会输出 \0 。
根据上面的书写方法,我们可以写成:
char a[6] = "HELLO";
两种书写方法,输出的效果是一样的。
2.4 数组
- 数组可以存储一固定大小、类型相同的集合。但它往往被认为是一系列相同类型的变量。
- 数组的声明并不是声明一个个单独的变量,比如 number0、number1、…、number99,而是声明一个数组变量,比如 numbers,然后使用 numbers[0]、numbers[1]、…、numbers[99] 来代表一个个单独的变量。数组中的特定元素可以通过索引访问。
在C++中,在声明一个变量的时候要指定元素类型和元素的数量。
type arrayName[size];
// 其中size必须为一个大于0的整型变量。
// type 可以是任意有效的 C++ 数据类型
2.5 变量的作用域
在C++ 有三个地方可以声明变量:
- 在一个函数或代码块内部声明的变量,叫局部变量;
- 在函数参数的定义中声明的变量,叫形式参数(形参);
- 在所有函数外部声明的变量,叫全局变量。
下面是一个例子:
#include <iostream>
#include <string>
using namespace std;
string a = "全局变量";
void vari(string b){
string c = "局部变量";
cout << "a 是:" << a << endl;
cout << "b 是:" << b << endl;
cout << "c 是:" << c << endl;
}
int main() {
vari("形式参数");
return 0;
}
运行结果:
a 是:全局变量
b 是:形式参数
c 是:局部变量
4. 常量
常量是什么,常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做字面量。
常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值。
常量就像是常规的变量,只不过常量的值在定义后不能进行修改。
4.1 常量的类型
在 C++ 中常量有多种类型,操作系统会根据常量的数据类型,来分配内存和决定在静态存储区中存储什么。
下面列出了 C++ 中常量的类型:
- 整型常量
- 浮点常量
- 布尔常量
- 字符常量
- 字符串常量
4.1.1 整型常量
整型常量可以是不同进制的常量。
在 C++ 中用不同的前缀来表示常量的进制,0x 表示十六进制,0 表示八进制,不带前缀则表示十进制。
在 C++ 中不仅有前缀还有后缀,后缀是 U 和 L 的组合。后缀不区分大小写,顺序可以任意。
- U:表示无符号整数(unsigned)
- L:表示长整数(long)
212 // 合法的
215u // 合法的
0xFeeL // 合法的
078 // 非法的:8 不是八进制的数字
032UU // 非法的:不能重复后缀
4.1.2 浮点常量
浮点常量由整数部分、小数点、小数部分和指数部分组成。我们可以使用小数形式或者指数形式来表示浮点常量。
当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。
当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。带符号的指数是用 e 或 E 引入的。
3.14159 // 合法的
314159E-5L // 合法的
510E // 非法的:不完整的指数
210f // 非法的:没有小数或指数
.e55 // 非法的:缺少整数或分数
4.1.3 布尔常量
布尔常量总共就两个:
- true:表示真;
- false:表示假。
4.1.4 字符常量
字符常量是括在单引号中。
如果常量以 L(大写的)开头,则表示它是一个宽字符常量(例如 L’x’),此时它必须存储在 wchar_t 类型的变量中。否则,它就是一个窄字符常量(例如 ‘x’),此时它可以存储在 char 类型的简单变量中。
字符常量可以是一个普通的字符(例如 ‘x’)、一个转义序列(例如 ‘\t’),或一个通用的字符(例如 ‘\u02C0’)。
转义字符
在 C++ 中,并不认为 '," ,\ 是字符。
- ’ 单引号是单个字符的界限 ‘s’ ;
- " 双引号是字符串的界限 “Hello” ;
- ** 反斜线是转义符号 ‘\t’ (水平制表符)。
其中,转义字符是一些特定的字符,有着特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。
所以的 ASCII码 都可以用 \ 加数字(八进制数字)来表示。而在 C 中定义了一些字母前加 \ 来表示ASCII编码表中一些常见的控制字符,如\0 ,\t ,\n 等,就称为转义字符,因为后面的字符,都不是它本来的ASCII字符意思了。
下表列出了一些这样的转义序列码:
转义序列 | 含义 |
---|---|
\ | \ 字符 |
\’ | ’ 字符 |
\" | " 字符 |
\? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo | 一到三位的八进制数 |
\xhh . . . | 一个或多个数字的十六进制数 |
举个例子:
// 转义字符
const char A = '\101'; // “101”是个八进制数字
cout << A << endl;
执行结果:
A
4.1.5 字符串常量
字符串常量是用双引号(“”)括起来的0个或者多个字符组成的序列。
在存储中编译器会为每个字符串结尾加上 ‘\0’ 作为字符串结束标志。
例子1:
const string HELLO = "Hello World!";
cout << HELLO << endl;
执行结果:
Hello World!
4.2 定义常量
在 C++ 中,有两种定义常量的方式:
- #define 预处理器;
- const 关键字。
4.2.1 #define 预处理器
- 预处理器
预处理器是一些指令,指示编译器在编译之前所需完成的预处理。
所有的预处理器指令都是以符号 # 开头。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。
#define 预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:
#define 常量名 常量的值;
例子:
#include <iostream>
using namespace std;
#define PI 3.1415926
int main(){
cout << PI << endl;
return 0;
}
执行结果:
3.1415926
4.2.2 const 关键字
用 const 关键字定义的常量比较特殊,声明的是一个变量,还具有常量的属性。值不可被改变,不能重新赋值,具有只读性,所以,有人又称 const 声明的是一个常变量。
定义方法为:
const 数据类型 常量名 = 值;
例子:
const int num = 10;
cout << num << endl;
执行结果:
10
const 关键字声明的常量,是可以通过指针改变的。
6. 存储类
存储类是用来定义 C++ 的变量和函数的范围和生命周期。
下面列出 C++ 程序中可用的存储类:
- auto
- register
- static
- extern
- mutable
- thread_local
6.1 auto 存储类
auto 关键字使用:
- 声明变量时根据初始化表达式自动推断该变量的类型;
- 声明函数时函数返回值的占位符;
auto f=3.14; //double
auto s("hello"); //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//错误,同时声明多个变量时,必须是初始化为同一类型
6.2 register 存储类
register 定义的是一个局部变量。这个局部变量不是存储在RAM中,而是存储在寄存器中。这也就意味着变量占用空间的最大尺寸等于寄存器的大小,并且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。
register int miles;
寄存器只用于需要快速访问的变量,比如计数器。
6.3 static 存储类
static 存储类告诉编译器在程序的生命周期内保持局部变量的存在,也就是说 static 关键字所修饰的的关键字,在程序结束之前是不会被创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
static 关键字除了修饰局部变量外,还可以修饰全局变量,在修饰全局变量时,会使变量的作用域限制在声明它的文件内。
6.4 extern 存储类
extern 存储类用于声明一个全局变量,这个全局变量对所有的程序文件都是可见的。
举个例子:
// main.cpp
#include <iostream>
int c;
extern void write_extern();
int main() {
c = 15;
write_extern();
}
在新建一个 support.cpp 来实现两程序间的共享:
// support.cpp
#include <iostream>
using namespace std;
extern int c;
void write_extern(void) {
cout << "Count is " << c << endl;
}
6.5 thread_local 存储类
使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
thread_local 说明符可以与 static 或 extern 合并。
可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
以下演示了可以被声明为 thread_local 的变量:
thread_local int x; // 命名空间下的全局变量
class X
{
static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s; // X::s 是需要定义的
void foo()
{
thread_local std::vector<int> v; // 本地变量
}
7. 控制结构
程序控制结构是指以某种顺序执行的一系列动作,用于解决某个问题。
理论和实践证明,无论多复杂的算法均可通过顺序、选择、循环 3种基本控制结构构造出来。
每种结构仅有一个入口和出口。由这3种基本结构组成的多层嵌套程序称为结构化程序。
7.1 循环
一般程序在执行的时候是,第一句、第二句 ······ 第N句,这样顺序执行的。可是,有的时候,一段代码可能需要重复执行很多次,这时候要是执行一遍写一次的话,这样工作量就太大啦。于是,为解决这个问题 C++ 引入了循环操作。
在 C++ 中有多种循环类型:while 循环、for 循环、do…while 循环、嵌套循环。
7.1.1 while 循环
只要给定的条件为真,while 循环语句会重复执行一个目标语句。
怎么使用 while 语句呢?那就接着往下看吧:
while( 条件 ){ // 当条件为真时,while会一直重复执行大括号中的内容,直到条件为假
代码块
}
举个例子:
#include <iostream>
using namespace std;
int main ()
{
// 局部变量声明
int a = 10;
// while 循环执行
while( a < 20 )
{
cout << "a 的值:" << a << endl;
a++;
}
return 0;
}
执行结果:
a 的值:10
a 的值:11
a 的值:12
a 的值:13
a 的值:14
a 的值:15
a 的值:16
a 的值:17
a 的值:18
a 的值:19
7.1.2 for 循环
语法
for ( init; condition; increment )
{
statement(s);
}
下面是 for 循环的控制流:
- init 会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。也可以不在这里写任何语句,只要有一个分号出现即可。
- 接下来,会判断 condition。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。
- 在执行完 for 循环主体后,控制流会跳回上面的 increment 语句。该语句允许您更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。
- 条件再次被判断。如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为假时,for 循环终止。
举个例子:
#include <iostream>
using namespace std;
int main ()
{
// for 循环执行
for( int a = 10; a < 20; a = a + 1 )
{
cout << "a 的值:" << a << endl;
}
return 0;
}
执行结果:
a 的值:10
a 的值:11
a 的值:12
a 的值:13
a 的值:14
a 的值:15
a 的值:16
a 的值:17
a 的值:18
a 的值:19
7.2 判断
判断结构要求程序员给出一个或多个条件,用户给出的值,符合条件,则为真,程序执行一段代码。如果,不符合条件,则为假,执行另一段代码。
在 C++ 中沿用了 C 中的判断语句,具体有:
语句 | 描述 |
---|---|
if 语句 | 一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。 |
if…else 语句 | 一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。 |
嵌套 if 语句 | 您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。 |
switch 语句 | 一个 switch 语句允许测试一个变量等于多个值时的情况。 |
嵌套 switch 语句 | 您可以在一个 switch 语句内使用另一个 switch 语句。 |
8. 函数
函数是一组一起执行一个任务的语句。每个 C++ 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。
您可以把代码划分到不同的函数中。怎么划分那就看你想怎么划分了,我通常是一个任务一个函数。
C++ 标准库提供了大量的程序可以调用的内置函数。例如,函数 strcat() 用来连接两个字符串,函数 memcpy() 用来复制内存到另一个位置。
函数还有很多叫法,比如方法、子例程或程序,等等。
8.1 函数定义
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体。
C++ 中的函数定义的一般形式如下:
return_type function_name( parameter list ){
body of the function
}
在 C++ 中,函数由一个函数头和一个函数主体组成。下面列出一个函数的所有组成部分:
- return_type:指的是返回值的类型,一个函数可以返回一个值。如果, return_type 为 void ,这时这个函数就可以没有返回值;
- function_name:这是函数的实际名称。函数名和参数列表一起构成了函数签名。
- parameter list:参数列表,参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。参数在声明的时候可以被初始化,不过初始化的参数应在列表的右侧。
- body of the function:函数主题,函数主体包含一组定义函数执行任务的语句。
举个例子:
两个数比较大小
// 函数返回两个数中较大的那个数
int max(int num1, int num2 = 2)
{
// 局部变量声明
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
8.2 函数声明
函数声明会告诉编译器,函数名称和如何调用函数。函数的实际主体可以单独定义。
函数声明包括:
函数类型 函数名(参数列表);
在函数声明时,参数的名子不重要,但是必须得有参数的类型。比如上面的 max() 函数声明,可以写成:
int max(int, int)
这样函数依然成立,且意思不变。
8.3 调用函数
当程序调用函数时,程序控制权会转移给被调用的函数。被调用的函数执行已定义的任务,当函数的返回语句被执行时,或到达函数的结束括号时,会把程序控制权交还给主程序。
如果,这个函数有返回值,则结束时,这个值就被存储在调用函数本身;
比如:
#include <iostream>
using namespace std;
int max(int num1, int num2 = 2) {
int result;
if (num1 > num2) {
result = num1;
} else {
result = num2;
}
return result;
}
int main(){
int m = max(4, 5);
cout << "最大的是:" << m << endl;
return 0;
}
编译执行后:
最大的是:5
此时,输出 max() 函数本身存储的是最大的值 。
8.4 内联函数
内联函数主要是起到,在函数调用时节约调用时的开销。
在 C++ 中,内联函数可以通过 inline关键字 来定义 (注意:是定义而非声明)。
下面通过一个例子来讲解:
- 用 inline 关键字定义 sum() 函数为内联函数;
- 定义num1、num2 两个变量,并赋值;
- 将 sum() 调用,并将 num1、num2 作为 sum() 的参数。
#include <iostream>
using namespace std;
inline int sum(int a, int b){
return a + b;
}
int main() {
int num1 = 1;
int num2 = 2;
cout << sum(num1, num2) << endl;
return 0;
}
在 main() 函数中,只需要写函数名,就可以调用。内联函数被调用,相当于把函数体中的内容,在重新写一遍来提高执行速度。
上面的 main() 函数可以写成:
int main() {
int num1 = 1;
int num2 = 2;
cout << num1 + num2 << endl;
return 0;
}
需要注意的是,内联函数只能是简单的函数。
8.5 重载函数
重载函数,函数名相同,参数不同。
就是创建多个名字相同,参数不同的函数,在调用该函数时,编译器根据你传入参数的类型、个数来确定执行那个函数。
举个例子:
#include <iostream>
#include <string>
using namespace std;
void f(){
cout << "第一个函数。" << endl;
}
void f(int a){
cout << "第二个函数。" << endl;
}
void f(string c, int a){
cout << "第三个函数。" << endl;
}
int main() {
int x = 3;
string y = "fdsg";
f(y, x);
f(x);
f();
return 0;
}
执行结果:
第三个函数。
第二个函数。
第一个函数。