C++ 是一种系统编程语言。用它的发明者, Bjarne Stroustrup 的话来说,C++ 的设计目标是:
- 成为“更好的 C 语言”
- 支持数据的抽象与封装
- 支持面向对象编程
- 支持泛型编程
C++ 提供了对硬件的紧密控制(正如 C 语言一样), 能够编译为机器语言,由处理器直接执行。 与此同时,它也提供了泛型、异常和类等高层功能。 虽然 C++ 的语法可能比某些出现较晚的语言更复杂,它仍然得到了人们的青睞—— 功能与速度的平衡使 C++ 成为了目前应用最广泛的系统编程语言之一。
////////////////
// 与C语言的比较
////////////////
// C++_几乎_是C语言的一个超集,它与C语言的基本语法有许多相同之处,
// 例如变量和函数的声明,原生数据类型等等。
// 和C语言一样,在C++中,你的程序会从main()开始执行,
// 该函数的返回值应当为int型,这个返回值会作为程序的退出状态值。
// 不过,大多数的编译器(gcc,clang等)也接受 void main() 的函数原型。
// (参见 http://en.wikipedia.org/wiki/Exit_status 来获取更多信息)
int main(int argc, char** argv)
{
// 和C语言一样,命令行参数通过argc和argv传递。
// argc代表命令行参数的数量,
// 而argv是一个包含“C语言风格字符串”(char *)的数组,
// 其中每个字符串代表一个命令行参数的内容,
// 首个命令行参数是调用该程序时所使用的名称。
// 如果你不关心命令行参数的值,argc和argv可以被忽略。
// 此时,你可以用int main()作为函数原型。
// 退出状态值为0时,表示程序执行成功
return 0;
}
// 然而,C++和C语言也有一些区别:
// 在C++中,字符字面量的大小是一个字节。
sizeof('c') == 1
// 在C语言中,字符字面量的大小与int相同。
sizeof('c') == sizeof(10)
// C++的函数原型与函数定义是严格匹配的
void func(); // 这个函数不能接受任何参数
// 而在C语言中
void func(); // 这个函数能接受任意数量的参数
// 在C++中,用nullptr代替C语言中的NULL
int* ip = nullptr;
// C++也可以使用C语言的标准头文件,
// 但是需要加上前缀“c”并去掉末尾的“.h”。
#include <cstdio>
int main()
{
printf("Hello, world!\n");
return 0;
}
///////////
// 函数重载
///////////
// C++支持函数重载,你可以定义一组名称相同而参数不同的函数。
void print(char const* myString)
{
printf("String %s\n", myString);
}
void print(int myInt)
{
printf("My int is %d", myInt);
}
int main()
{
print("Hello"); // 解析为 void print(const char*)
print(15); // 解析为 void print(int)
}
///////////////////
// 函数参数的默认值
///////////////////
// 你可以为函数的参数指定默认值,
// 它们将会在调用者没有提供相应参数时被使用。
void doSomethingWithInts(int a = 1, int b = 4)
{
// 对两个参数进行一些操作
}
int main()
{
doSomethingWithInts(); // a = 1, b = 4
doSomethingWithInts(20); // a = 20, b = 4
doSomethingWithInts(20, 5); // a = 20, b = 5
}
// 默认参数必须放在所有的常规参数之后。
void invalidDeclaration(int a = 1, int b) // 这是错误的!
{
}
///////////
// 命名空间
///////////
// 命名空间为变量、函数和其他声明提供了分离的的作用域。
// 命名空间可以嵌套使用。
namespace First {
namespace Nested {
void foo()
{
printf("This is First::Nested::foo\n");
}
} // 结束嵌套的命名空间Nested
} // 结束命名空间First
namespace Second {
void foo()
{
printf("This is Second::foo\n")
}
}
void foo()
{
printf("This is global foo\n");
}
int main()
{
// 如果没有特别指定,就从“Second”中取得所需的内容。
using namespace Second;
foo(); // 显示“This is Second::foo”
First::Nested::foo(); // 显示“This is First::Nested::foo”
::foo(); // 显示“This is global foo”
}
////////////
// 输入/输出
////////////
// C++使用“流”来输入输出。<<是流的插入运算符,>>是流提取运算符。
// cin、cout、和cerr分别代表
// stdin(标准输入)、stdout(标准输出)和stderr(标准错误)。
#include <iostream> // 引入包含输入/输出流的头文件
using namespace std; // 输入输出流在std命名空间(也就是标准库)中。
int main()
{
int myInt;
// 在标准输出(终端/显示器)中显示
cout << "Enter your favorite number:\n";
// 从标准输入(键盘)获得一个值
cin >> myInt;
// cout也提供了格式化功能
cout << "Your favorite number is " << myInt << "\n";
// 显示“Your favorite number is <myInt>”
cerr << "Used for error messages";
}
/////////
// 字符串
/////////
// C++中的字符串是对象,它们有很多成员函数
#include <string>
using namespace std; // 字符串也在std命名空间(标准库)中。
string myString = "Hello";
string myOtherString = " World";
// + 可以用于连接字符串。
cout << myString + myOtherString; // "Hello World"
cout << myString + " You"; // "Hello You"
// C++中的字符串是可变的,具有“值语义”。
myString.append(" Dog");
cout << myString; // "Hello Dog"
/////////////
// 引用
/////////////
// 除了支持C语言中的指针类型以外,C++还提供了_引用_。
// 引用是一种特殊的指针类型,一旦被定义就不能重新赋值,并且不能被设置为空值。
// 使用引用时的语法与原变量相同:
// 也就是说,对引用类型进行解引用时,不需要使用*;
// 赋值时也不需要用&来取地址。
using namespace std;
string foo = "I am foo";
string bar = "I am bar";
string& fooRef = foo; // 建立了一个对foo的引用。
fooRef += ". Hi!"; // 通过引用来修改foo的值
cout << fooRef; // "I am foo. Hi!"
// 这句话的并不会改变fooRef的指向,其效果与“foo = bar”相同。
// 也就是说,在执行这条语句之后,foo == "I am bar"。
fooRef = bar;
const string& barRef = bar; // 建立指向bar的常量引用。
// 和C语言中一样,(指针和引用)声明为常量时,对应的值不能被修改。
barRef += ". Hi!"; // 这是错误的,不能修改一个常量引用的值。
///////////////////
// 类与面向对象编程
///////////////////
// 有关类的第一个示例
#include <iostream>
// 声明一个类。
// 类通常在头文件(.h或.hpp)中声明。
class Dog {
// 成员变量和成员函数默认情况下是私有(private)的。
std::string name;
int weight;
// 在这个标签之后,所有声明都是公有(public)的,
// 直到重新指定“private:”(私有继承)或“protected:”(保护继承)为止
public:
// 默认的构造器
Dog();
// 这里是成员函数声明的一个例子。
// 可以注意到,我们在此处使用了std::string,而不是using namespace std
// 语句using namespace绝不应当出现在头文件当中。
void setName(const std::string& dogsName);
void setWeight(int dogsWeight);
// 如果一个函数不对对象的状态进行修改,
// 应当在声明中加上const。
// 这样,你就可以对一个以常量方式引用的对象执行该操作。
// 同时可以注意到,当父类的成员函数需要被子类重写时,
// 父类中的函数必须被显式声明为_虚函数(virtual)_。
// 考虑到性能方面的因素,函数默认情况下不会被声明为虚函数。
virtual void print() const;
// 函数也可以在class body内部定义。
// 这样定义的函数会自动成为内联函数。
void bark() const { std::cout << name << " barks!\n" }
// 除了构造器以外,C++还提供了析构器。
// 当一个对象被删除或者脱离其定义域时,它的析构函数会被调用。
// 这使得RAII这样的强大范式(参见下文)成为可能。
// 为了衍生出子类来,基类的析构函数必须定义为虚函数。
virtual ~Dog();
}; // 在类的定义之后,要加一个分号
// 类的成员函数通常在.cpp文件中实现。
void Dog::Dog()
{
std::cout << "A dog has been constructed\n";
}
// 对象(例如字符串)应当以引用的形式传递,
// 对于不需要修改的对象,最好使用常量引用。
void Dog::setName(const std::string& dogsName)
{
name = dogsName;
}
void Dog::setWeight(int dogsWeight)
{
weight = dogsWeight;
}
// 虚函数的virtual关键字只需要在声明时使用,不需要在定义时重复
void Dog::print() const
{
std::cout << "Dog is " << name << " and weighs " << weight << "kg\n";
}
void Dog::~Dog()
{
std::cout << "Goodbye " << name << "\n";
}
int main() {
Dog myDog; // 此时显示“A dog has been constructed”
myDog.setName("Barkley");
myDog.setWeight(10);
myDog.print(); // 显示“Dog is Barkley and weighs 10 kg”
return 0;
} // 显示“Goodbye Barkley”
// 继承:
// 这个类继承了Dog类中的公有(public)和保护(protected)对象
class OwnedDog : public Dog {
void setOwner(const std::string& dogsOwner)
// 重写OwnedDogs类的print方法。
// 如果你不熟悉子类多态的话,可以参考这个页面中的概述:
// http://zh.wikipedia.org/wiki/%E5%AD%90%E7%B1%BB%E5%9E%8B
// override关键字是可选的,它确保你所重写的是基类中的方法。
void print() const override;
private:
std::string owner;
};
// 与此同时,在对应的.cpp文件里:
void OwnedDog::setOwner(const std::string& dogsOwner)
{
owner = dogsOwner;
}
void OwnedDog::print() const
{
Dog::print(); // 调用基类Dog中的print方法
// "Dog is <name> and weights <weight>"
std::cout << "Dog is owned by " << owner << "\n";
// "Dog is owned by <owner>"
}
/////////////////////
// 初始化与运算符重载
/////////////////////
// 在C++中,通过定义一些特殊名称的函数,
// 你可以重载+、-、*、/等运算符的行为。
// 当运算符被使用时,这些特殊函数会被调用,从而实现运算符重载。
#include <iostream>
using namespace std;
class Point {
public:
// 可以以这样的方式为成员变量设置默认值。
double x = 0;
double y = 0;
// 定义一个默认的构造器。
// 除了将Point初始化为(0, 0)以外,这个函数什么都不做。
Point() { };
// 下面使用的语法称为初始化列表,
// 这是初始化类中成员变量的正确方式。
Point (double a, double b) :
x(a),
y(b)
{ /* 除了初始化成员变量外,什么都不做 */ }
// 重载 + 运算符
Point operator+(const Point& rhs) const;
// 重载 += 运算符
Point& operator+=(const Point& rhs);
// 增加 - 和 -= 运算符也是有意义的,但这里不再赘述。
};
Point Point::operator+(const Point& rhs) const
{
// 创建一个新的点,
// 其横纵坐标分别为这个点与另一点在对应方向上的坐标之和。
return Point(x + rhs.x, y + rhs.y);
}
Point& Point::operator+=(const Point& rhs)
{
x += rhs.x;
y += rhs.y;
return *this;
}
int main () {
Point up (0,1);
Point right (1,0);
// 这里使用了Point类型的运算符“+”
// 调用up(Point类型)的“+”方法,并以right作为函数的参数
Point result = up + right;
// 显示“Result is upright (1,1)”
cout << "Result is upright (" << result.x << ',' << result.y << ")\n";
return 0;
}
///////////
// 异常处理
///////////
// 标准库中提供了一些基本的异常类型
// (参见http://en.cppreference.com/w/cpp/error/exception)
// 但是,其他任何类型也可以作为一个异常被拋出
#include <exception>
// 在_try_代码块中拋出的异常可以被随后的_catch_捕获。
try {
// 不要用 _new_关键字在堆上为异常分配空间。
throw std::exception("A problem occurred");
}
// 如果拋出的异常是一个对象,可以用常量引用来捕获它
catch (const std::exception& ex)
{
std::cout << ex.what();
// 捕获尚未被_catch_处理的所有错误
} catch (...)
{
std::cout << "Unknown exception caught";
throw; // 重新拋出异常
}
///////
// RAII
///////
// RAII指的是“资源获取就是初始化”(Resource Allocation Is Initialization),
// 它被视作C++中最强大的编程范式之一。
// 简单说来,它指的是,用构造函数来获取一个对象的资源,
// 相应的,借助析构函数来释放对象的资源。
// 为了理解这一范式的用处,让我们考虑某个函数使用文件句柄时的情况:
void doSomethingWithAFile(const char* filename)
{
// 首先,让我们假设一切都会顺利进行。
FILE* fh = fopen(filename, "r"); // 以只读模式打开文件
doSomethingWithTheFile(fh);
doSomethingElseWithIt(fh);
fclose(fh); // 关闭文件句柄
}
// 不幸的是,随着错误处理机制的引入,事情会变得复杂。
// 假设fopen函数有可能执行失败,
// 而doSomethingWithTheFile和doSomethingElseWithIt会在失败时返回错误代码。
// (虽然异常是C++中处理错误的推荐方式,
// 但是某些程序员,尤其是有C语言背景的,并不认可异常捕获机制的作用)。
// 现在,我们必须检查每个函数调用是否成功执行,并在问题发生的时候关闭文件句柄。
bool doSomethingWithAFile(const char* filename)
{
FILE* fh = fopen(filename, "r"); // 以只读模式打开文件
if (fh == nullptr) // 当执行失败是,返回的指针是nullptr
return false; // 向调用者汇报错误
// 假设每个函数会在执行失败时返回false
if (!doSomethingWithTheFile(fh)) {
fclose(fh); // 关闭文件句柄,避免造成内存泄漏。
return false; // 反馈错误
}
if (!doSomethingElseWithIt(fh)) {
fclose(fh); // 关闭文件句柄
return false; // 反馈错误
}
fclose(fh); // 关闭文件句柄
return true; // 指示函数已成功执行
}
// C语言的程序员通常会借助goto语句简化上面的代码:
bool doSomethingWithAFile(const char* filename)
{
FILE* fh = fopen(filename, "r");
if (fh == nullptr)
return false;
if (!doSomethingWithTheFile(fh))
goto failure;
if (!doSomethingElseWithIt(fh))
goto failure;
fclose(fh); // 关闭文件
return true; // 执行成功
failure:
fclose(fh);
return false; // 反馈错误
}
// 如果用异常捕获机制来指示错误的话,
// 代码会变得清晰一些,但是仍然有优化的余地。
void doSomethingWithAFile(const char* filename)
{
FILE* fh = fopen(filename, "r"); // 以只读模式打开文件
if (fh == nullptr)
throw std::exception("Could not open the file.");
try {
doSomethingWithTheFile(fh);
doSomethingElseWithIt(fh);
}
catch (...) {
fclose(fh); // 保证出错的时候文件被正确关闭
throw; // 之后,重新抛出这个异常
}
fclose(fh); // 关闭文件
// 所有工作顺利完成
}
// 相比之下,使用C++中的文件流类(fstream)时,
// fstream会利用自己的析构器来关闭文件句柄。
// 只要离开了某一对象的定义域,它的析构函数就会被自动调用。
void doSomethingWithAFile(const std::string& filename)
{
// ifstream是输入文件流(input file stream)的简称
std::ifstream fh(filename); // 打开一个文件
// 对文件进行一些操作
doSomethingWithTheFile(fh);
doSomethingElseWithIt(fh);
} // 文件已经被析构器自动关闭
// 与上面几种方式相比,这种方式有着_明显_的优势:
// 1. 无论发生了什么情况,资源(此例当中是文件句柄)都会被正确关闭。
// 只要你正确使用了析构器,就_不会_因为忘记关闭句柄,造成资源的泄漏。
// 2. 可以注意到,通过这种方式写出来的代码十分简洁。
// 析构器会在后台关闭文件句柄,不再需要你来操心这些琐事。
// 3. 这种方式的代码具有异常安全性。
// 无论在函数中的何处拋出异常,都不会阻碍对文件资源的释放。
// 地道的C++代码应当把RAII的使用扩展到各种类型的资源上,包括:
// - 用unique_ptr和shared_ptr管理的内存
// - 各种数据容器,例如标准库中的链表、向量(容量自动扩展的数组)、散列表等;
// 当它们脱离作用域时,析构器会自动释放其中储存的内容。
// - 用lock_guard和unique_lock实现的互斥
基础
基本框架(不管写什么先写这十行)
#include<iostream> using namespace std; int main(){ system("pause"); return 0; }
Hello World
#include<iostream>
using namespace std;
int main(){
cout << "Hello World" << endl;
system("pause");
return 0;
}
数据类型
整数
// 短整型
short a = 1;
// 整型
int b = 1;
// 长整型
long c = 1;
// 长长整型
long long d = 1;
浮点型
// 单精度浮点型
float a = 1.1;
// 双精度浮点型
double b = 1.1;
字符型
// 字符型
char a = '字';
// 字符串型
char b[] = "字符串";
字符串
#include<iostream>
// 使用string需要导入头文件
#include<string>
using namespace std;
int main(){
string a = "Hello World";
cout << a << endl;
system("pause");
return 0;
}
布尔型
#include<iostream>
using namespace std;
int main(){
bool a = true;
cout << a << endl;
system("pause");
return 0;
}
转义字符
作用:用于表示一些不能显示出来的 ASCII 字符
常用的转义字符:
\n \\ \t
转义字符 | 含义 | ASCII 码表 |
---|---|---|
\a |
警报 | 007 |
\b |
退格 | 008 |
\f |
换页 | 012 |
\n |
换行 | 010 |
\r |
回车 | 013 |
\t |
水平制表(HT)(跳到下一个 TAB(8 个空格)位置) | 009 |
\v |
垂直制表 | 011 |
\\ |
代表一个反斜线字符 | 092 |
' |
代表单引号字符 | 039 |
" |
代表双引号字符 | 034 |
? |
代表问号 | 063 |
\0 |
数字 0 | 000 |
\ddd |
8 进制转义字符,d 范围 0~7 | 3 位 8 进制 |
\xhh |
16 进制转义字符,h 范围 09,af,A~F | 3 位 16 进制 |
查询大小
siziof
关键字:查询变量内存占用
#include<iostream>
using namespace std;
int main(){
char a[] = "Hello World"
// siziof关键字:查询变量内存占用
cout << "a变量占用内存大小为:" << sizeof(a) << endl;
system("pause");
return 0;
}
数据键盘输入
cin
关键字:从键盘获取一个整数
#include<iostream>
using namespace std;
int main(){
int a = 0;
cout << "输入a的值" << endl;
// 关键字为cin
cin >> a;
cout << a << endl;
system("pause");
return 0;
}
运算符
算数运算符
运算符 | 描述 |
---|---|
+ |
相加 |
- |
相减 |
* |
相乘 |
/ |
相除 |
% |
取模,整除后的余数 |
++ |
自增 |
-- |
自减 |
赋值运算符
运算符 | 描述 |
---|---|
= |
把右边操作数的值赋给左边操作数 |
+= |
把右边操作数加上左边操作数的结果赋值给左边操作数 |
-= |
把左边操作数减去右边操作数的结果赋值给左边操作数 |
*= |
把右边操作数乘以左边操作数的结果赋值给左边操作数 |
/= |
把左边操作数除以右边操作数的结果赋值给左边操作数 |
%= |
求两个操作数的模赋值给左边操作数 |
<<= |
左移且赋值运算符 |
>>= |
右移且赋值运算符 |
&= |
按位与且赋值运算符 |
^= |
按位异或且赋值运算符 |
|= |
按位或且赋值运算符 |
比较运算符
运算符 | 描述 |
---|---|
== |
检查两个操作数的值是否相等,如果相等则条件为真。 |
!= |
检查两个操作数的值是否相等,如果不相等则条件为真。 |
> |
检查左操作数的值是否大于右操作数的值,如果是则条件为真。 |
< |
检查左操作数的值是否小于右操作数的值,如果是则条件为真。 |
>= |
检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 |
<= |
检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 |
逻辑运算符
运算符 | 描述 |
---|---|
&& |
称为逻辑与运算符。如果两个操作数都 true,则条件为 true。 |
|| |
称为逻辑或运算符。如果两个操作数中有任意一个 true,则条件为 true。 |
! |
称为逻辑非运算符。用来逆转操作数的逻辑状态,如果条件为 true 则逻辑非运算符将使其为 false。 |
if 结构
if else if else 语句
#include<iostream>
using namespace std;
int main(){
int a = 0;
if(a < 0){
cout << "a小于0" << endl;
}else if(a == 0){
cout << "a等于0" << endl;
}else{
cout << "a大于0" << endl;
}
system("pause");
return 0;
}
三元运算符
#include<iostream>
using namespace std;
int main(){
int a = 0;
cout << a==0? "a等于0":"a不等于0" << endl;
system("pause");
return 0;
}
switch 语句
#include<iostream>
using namespace std;
int main(){
int a = 0;
cout << a==0? "a等于0":"a不等于0" << endl;
system("pause");
return 0;
}
循环语句
While 循环
#include<iostream>
using namespace std;
int main(){
int a = 0;
while(a <= 10){
cout << a << endl;
a++;
}
system("pause");
return 0;
}
do While 循环
#include<iostream>
using namespace std;
int main(){
int a = 0;
do{
cout << a << endl;
a++;
}while(a <= 10)
system("pause");
return 0;
}
for 循环
#include<iostream>
using namespace std;
int main(){
for(int i = 0; i < 10; i++){
cout << a << endl;
}
system("pause");
return 0;
}
数组
同类型数据的集合
- 每个数据元素都是相同的数据类型
- 是由连续的内存位置组成的
一维数组
格式
数据类型 数组名称[数组长度];
数据类型 数组名称[数组长度] = {值1, 值2, 值3, ...};
数据类型 数组名称[] = {值1, 值2, 值3, ...};
示范
int a[10];
int b[3] = {1, 2, 3};
int c[] = {1, 2, 3, 4};
二维数组
格式
数据类型 数组名称[行数][列数];
数据类型 数组名称[行数][列数] = {{值1, 值2, 值3, ...},{值1, 值2, 值3, ...}};
数据类型 数组名称[][列数] = {{值1, 值2, 值3, ...},{值1, 值2, 值3, ...}};
示范
省行不省列
int a[10][10];
int b[2][3] = {{1, 2, 3},{1, 2, 3}};
int c[][3] = {1, 2, 3, 4, 5, 6};
函数
格式
返回值类型 函数名称(参数1, 参数2, ...){
代码
return 返回值
}
写法
int sum(int a, int b){
return a + b;
}
调用
#include<iostream>
using namespace std;
int sum(int a, int b){
int sum = a + b;
return sum;
}
int main(){
// 调用函数,返回两个参数之和并打印
cout << sum(1, 2) << endl;
system("pause");
return 0;
}
指针
可以通过指针间接访问内存,指针就是一个存放地址的变量
数据类型 * 指针变量名称 #include<iostream> using namespace std; int main(){ int a = 10; // 创建指针并记录a的内存地址 int * p = &a; // 使用指针,解引用(*p等于内存地址的值,也就是a的值) *p = 1000; // 修改内存地址的值也等于修改变量a的值 cout << *p << endl; // 结果:1000,a也是1000 system("pause"); return 0; }
常量修饰符
在创建指针时加上 const 修饰符,表示这个指针不能被改变
// 常量指针,值不能改,地址能改
const int * p = &;
// 指针常量,值能改,地址不能改
int * const p = &;
// 常量,值不能改,地址不能改
const int * const p = &;
空指针
指的是指针创建时为 NULL,空指针不允许被访问
int * p = NULL;
野指针
指针指向非法内存空间,直接操作非法空间可能没用权限,或引发不可预料的情况
int * p = (int*)0x1100;
指针数组
数组在内存地址中是连续的,可以通过指针来访问数组
#include<iostream>
using namespace std;
int main(){
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 指针指向列表第一位
int * p = arr;
cout << *p << endl;
// 指针内存地址向前一位
p++; // 指针向后偏移4个字节
// 此时访问到列表第二位
cout << *p << endl;
system("pause");
return 0;
}
指针函数
函数的参数可以传入指针
#include<iostream>
using namespace std;
void swap(int * p1, int * p2){
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main(){
int a = 10;
int b = 20;
swap(&a, &b);
cout << a << endl;
cout << b << endl;
system("pause");
return 0;
}
结构体
属于用户自定义的数据类型(内置的数据类型的集合组成的新类型),相当于类的前身
结构体基本不用,完全被 class 取代了
struct 结构体名称{ 数据类型 变量名称; 数据类型 变量名称; ... }; // 创建结构体 struct Struct{ int a; float b; char c; } // 创建结构体,顺便实例 struct Struct{ int a; float b; char c; }s1; // 使用1,struct关键字可省略 struct Struct s1 = {1, 1.1, "123"}; // 使用2,struct关键字可省略 struct Struct s2; s2.a = 1; s2.b = 1.1; s2.c = "123";
常量修饰符
在创建指针时加上 const 修饰符,表示这个结构体不能被改变
// 创建结构体
struct Struct{
int a;
float b;
char c;
}
// 实例结构体
const struct Struct s = { 1, 1.1, "111"}
结构体数组
将结构体放进数组中
// 创建结构体
struct Struct{
int a;
float b;
char c;
}
// 用数组方式实例结构体
struct Struct stuarr[3] = {
{1, 1.1, "111"},
{2, 2.2, "122"},
{3, 3.3, "133"}
}
// 通过下标修改结构体属性
stuarr[0].a = 0
结构体指针
通过指针操作结构体
- 通过
->
操作符访问结构体指针的属性
// 创建结构体
struct Struct{
int a;
float b;
char c;
};
// 实例结构体
struct Struct s = {1, 1.1, "123"};
// 创建结构体指针指向s结构体
struct * p = &s;
// 通过->来访问结构体指针的属性
cout << p->a << endl;
cout << p->b << endl;
cout << p->c << endl;
结构体嵌套
struct Struct0{
int aa;
float bb;
char cc;
};
struct Struct1{
int a;
float b;
char c;
struct Struct0 stu;
};
// 实例结构体
Struct1 s;
// 修改结构体
s.a = 1;
s.b = 1.1;
s.c = "111";
// 修改结构体内的结构体
s.stu.aa = 2;
s.stu.bb = 2.2;
s.stu.cc = "222"
结构体函数
函数的参数可以传入结构体
#include<iostream>
using namespace std;
struct Struct{
int a;
float b;
char c;
};
void swap(struct Struct s){
cout << s.a << s.b << s.c << endl;
}
int main(){
struct Struct0 s;
s.a = 1;
s.b = 1.1;
s.c = "111";
swap(s);
system("pause");
return 0;
}
new 操作符
利用 new 在堆区开辟数据,new 返回的是数据的指针
new 数据类型
引用
给变量取别名
格式
数据类型 &变量别名 = 变量原名
写法
int a = 10;
int &b = a;
// 创建函数,参数是引用
void function(int &x, int &y)
{
// 变量被重新赋值
x = 10;
y = 20;
}
void main()
{
int a = 1;
int b = 2;
// 传入变量
function(a, b);
cout << a << endl << b << endl;
// 结果是输入10和20,a和b的值被函数内改变
}
不使用引用
// 创建函数,参数是引用
void function(int &x, int &y)
{
// 变量被重新赋值
x = 10;
y = 20;
}
void main()
{
int a = 1;
int b = 2;
// 传入变量
function(a, b);
cout << a << endl << b << endl;
// 结果是输入10和20,a和b的值被函数内改变
}
进阶
面向对象、class
类是代码的设计图,对象是按照类生成出来的代码,我们一般把生成过程叫做实例化
然而这个设计图,是可以被其它设计图复制然后加以改进的,这个复制的过程就是继承
然而有些类我们希望他有很多特性,继承了非常多类,这个行为叫做多态
在实际中,被复制的设计图就是父类,复制其它设计图的设计图就是子类
类都是设计图,只有在实例化成为对象时,这个代码才运行,才可以用它自身的属性和方法
类的结构
class 类名称{
权限:
变量 or 函数(方法)
权限:
变量 or 函数(方法)
...
}
#include<iostream>
using namespace std;
// 创建类
class ClassName{
// 权限
public:
// 类变量(成员变量)
int attribute = 1;
// 类方法(成员方法)
void function(int a)
{
attribute = a;
}
};
int main() {
// 实例化类
ClassName cln;
// 调用成员属性
cout << cln.attribute << endl;
// 调用成员方法
cln.function(10);
cout << cln.attribute << endl;
system("pause");
return 0;
}
权限
-
public
(公开)
在类外面可以被直接调用protected
(保护)
在类外面可以不可以被调用,只有子类(继承这个类的类)才可以调用-
private
(私有)
只能在类里面被调用
// 创建类
class ClassName{
// 公开,在类外面可以被直接调用
public:
变量 or 函数(方法);
// 保护,在类外面可以不可以被调用,只有子类(继承这个类的类)才可以调用
protected:
变量 or 函数(方法);
// 私有,只能在类里面被调用
private:
变量 or 函数(方法);
};
构造函数和析构函数
在类中,有两个隐藏的函数可以编写,功能分别是类在实例后自动调用一次,和类在销毁前自动调用一次
class ClassName{
// 与类名一致,且没有返回值的函数就是构造函数
// 类在实例后会自动调用一次,也就是初始化
ClassName(){
...
};
// 与类名一致,前面加上~,且没有返回值的函数就是析构函数
// 类在销毁前会自动调用一次
// 注意!不允许有参数
~ClassName(){
...
}
};
静态成员
定义一个全局变量,所有对象都可以访问这个变量
// 创建静态成员
static int a = 0;
static void func(){
...
}
// 访问方式
类名::静态成员名称
继承
继承某个类的属性或方法
class 子类 : 继承方式 父类{
...
};
// ClassB继承ClassA类
class ClassB : public ClassA{
...
};
继承方式示例代码:公有继承,保护继承,私有继承
// 创建类
class ClassA
{
public:
int a = 1;
protected:
int b = 2;
private:
int c = 3;
};
// 继承ClassA类的公有属性和受保护属性,但无法继承父类的私有属性
// 将继承到的属性变为自身类的public公有属性,可以被自身外调用
class ClassB : public ClassA
{
public:
// 构造函数,在实例化类时打印自身属性或方法
ClassB()
{
cout << a << endl;
cout << b << endl;
}
};
void main()
{
// 实例化ClassB类,触发构造函数
ClassB classb;
// 公有继承可以在类外调用属性
classb.a;
classb.b;
}
// 创建类
class ClassA
{
public:
int a = 1;
protected:
int b = 2;
private:
int c = 3;
};
// 继承ClassA类的公有属性和受保护属性,但无法继承父类的私有属性
// 将继承到的属性变为自身类的protected受保护属性,除了子类,不允许在类外调用
class ClassB : protected ClassA
{
public:
// 构造函数,在实例化类时打印自身属性或方法
ClassB()
{
cout << a << endl;
cout << b << endl;
}
};
void main()
{
// 实例化ClassB类,触发构造函数
ClassB classb;
// 无法在类外调用受保护的属性
}
// 创建类
class ClassA
{
public:
int a = 1;
protected:
int b = 2;
private:
int c = 3;
};
// 继承ClassA类的公有属性和受保护属性,但无法继承父类的私有属性
// 将继承到的属性变为自身类的private私有属性,在类外不允许被调用
class ClassB : private ClassA
{
public:
// 构造函数,在实例化类时打印自身属性或方法
ClassB()
{
cout << a << endl;
cout << b << endl;
}
};
void main()
{
// 实例化ClassB类,触发构造函数
ClassB classb;
// 无法在类外调用私有的属性
}
多态
多态主要是使用虚函数,让子类可以重写指定的成员函数
虚函数
********************************格式:
virtual void function()
********************************示范:
class Class
{
public:
virtual void function()
{
cout << "Class" << endl;
}
};
********************************不使用多态:
// 创建类
class ClassA
{
public:
void function()
{
cout << "ClassA" << endl;
}
};
// 创建子类并重写函数
class ClassB : public ClassA
{
public:
void function()
{
cout << "ClassB" << endl;
}
};
void main()
{
// 父类类型指针传入子类,并调用函数,结果使用的是父类的函数
// 函数不会被重写,需要使用virtual虚函数
ClassA *Class = new ClassB;
Class->function();
}
********************************使用多态:
// 创建类
class ClassA
{
public:
virtual void function()
{
cout << "ClassA" << endl;
}
};
// 创建子类并重写函数
class ClassB : public ClassA
{
public:
void function()
{
cout << "ClassB" << endl;
}
};
void main()
{
// 父类类型指针传入子类,并调用函数,结果使用的是子类的函数
// 使用了virtual后,函数被重写
ClassA *Class = new ClassB;
Class->function();
}
纯虚函数
指定一个函数作为纯虚函数,作用是给子类去重写,不实现逻辑
类中有纯虚函数,这个类就是抽象类,是用来给子类去重写其中函数
子类必须重写纯虚函数,否则无法实例化类
********************************格式:
virtual 返回类型 函数名(参数列表) = 0;
********************************写法:
virtual void function() = 0;
********************************示范:
// 创建类
class ClassA
{
public:
// 定义纯虚函数
virtual void function() = 0;
};
// 继承并重写纯虚函数
class ClassB : public ClassA
{
public:
void function()
{
cout << "ClassB" << endl;
}
};
void main()
{
// 报错! ClassA无法被实例化,因为类中有纯虚函数,是抽象类
ClassA Clsa;
Clsa.function();
// 因为子类重写了纯虚函数,所以可以直接被调用
ClassB Clsb;
Clsb.function();
}
友元
指定函数或类可以访问自身的私有属性
目的就是让一个函数或者类,访问另一个类中的私有变量
********************************格式:
class ClassA
{
friend 函数定义
friend 类定义
}
********************************写法:
class ClassA
{
// 指定全局函数为友元,友元可访问私有属性a
friend viod function();
// 指定类为友元,友元可访问私有属性a
friend class ClassB;
private:
int a;
}
运算符重载
对运算符重新定义,赋予其它功能,以适应不同数据类型,例如时间 + 时间。
通过固定形式的函数来定义运算符重载
格式
函数返回类型 operator 运算符(参数)
{
函数处理
}
类内成员函数写法
class ClassA
{
public:
int a = 0;
int operator+(int i)
{
return this->a + i;
}
};
int main()
{
ClassA A;
cout << A+10+10 << endl;
}
友元全局函数写法
class ClassA
{
friend int operator+(ClassA &A, int i);
public:
int a = 0;
};
int operator+(ClassA &A, int i)
{
return A.a + i;
}
int main()
{
ClassA A;
cout << A + 10 << endl;
}
文件操作
C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,这 3 个类分别为:
ifstream
:专用于从文件中读取数据;-
ofstream
:专用于向文件中写入数据;fstream
:既可用于从文件中读取数据,又可用于向文件中写入数据。
头文件
#include<fstream>
写入文件
#include<fstream>
using namespace std;
void main()
{
// 创建文件流类
fstream f;
// 打开指定文件
f.open("123.txt");
// 输入内容
f << "123";
// 读取内容
// 关闭文件流
f.close();
}
读取文件
#include<fstream>
using namespace std;
void main()
{
// 创建文件流类
fstream f;
string content;
// 打开指定文件
f.open("123.txt");
// 将内容保存至变量
f >> content;
// 关闭文件流
f.close();
// 打印内容
cout << content;
}
成员函数 | 功能 |
---|---|
open() | 打开指定文件,使其与文件流对象相关联。 |
is_open() | 检查指定文件是否已打开。 |
close() | 关闭文件,切断和文件流对象的关联。 |
swap() | 交换 2 个文件流对象。 |
operator>> | 重载 >> 运算符,用于从指定文件中读取数据。 |
gcount() | 返回上次从文件流提取出的字符个数。该函数常和 get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 联用。 |
get() | 从文件流中读取一个字符,同时该字符会从输入流中消失。 |
getline(str,n,ch) | 从文件流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 ‘\0’。 |
ignore(n,ch) | 从文件流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。 |
peek() | 返回文件流中的第一个字符,但并不是提取该字符。 |
putback© | 将字符 c 置入文件流(缓冲区)。 |
operator<< | 重载 << 运算符,用于向文件中写入指定数据。 |
put() | 向指定文件流中写入单个字符。 |
write() | 向指定文件中写入字符串。 |
tellp() | 用于获取当前文件输出流指针的位置。 |
seekp() | 设置输出文件输出流指针的位置。 |
flush() | 刷新文件输出流缓冲区。 |
good() | 操作成功,没有发生任何错误。 |
eof() | 到达输入末尾或文件尾。 |
模板
类模板
建立一个通用类,类中的数据类型在实例化时再制定
格式
template<typename T>
class cls
{
T name;
};
示例代码
// 创建类模板
template<typename T>
class cls
{
public:
T name = 1;
};
void main()
{
// 实例化类,定义T的类型为int
cls<int> c;
// 打印变量的值和类型
cout << c.name << endl << typeid(c.name).name() << endl;
}
函数模板
建立一个通用函数,在实例化时再制定数据类型
格式
template<typename T>
void func(T &a)
{
cout << typeid(a).name() << endl;
}
示例代码
// 创建模板
template<typename T>
void func(T &a)
{
// 打印参数数据类型
cout << typeid(a).name() << endl;
}
void main()
{
// 创建int变量
int a = 1;
// 使用模板函数,定义int类型,传入int变量
func<int>(a);
}
标准模板库(STL)
STL(Standard Template Library) 标准模板库
STL 是 C++ 开发过程中常用的数据结构和算法库,以往编程中没有这套标准,每次开发都需要写一套数据结构和算法,直到 1998 年惠普实验室开发了这套库,至今已被内置到 C++ 中变为标准库,无需额外安装。
功能上来说,就是 C++ 内置的数据结构和算法。
Array(静态数组)
固定容量的数组,不可延长/缩短数组长度
头文件
#include<array>
创建静态数组
#include<array>
void main()
{
// 创建数组,指定数据类型与长度
array<int, 5> a;
}
遍历数组
#include<array>
using namespace std;
void main()
{
// 创建数组
array<int, 5> a = {1, 2, 3, 4, 5};
// 遍历数组
for (int i : a)
{
cout << i << endl;
}
}
成员函数 | 功能 |
---|---|
begin() | 返回指向容器中第一个元素的随机访问迭代器。 |
end() | 返回指向容器最后一个元素之后一个位置的随机访问迭代器,通常和 begin() 结合使用。 |
rbegin() | 返回指向最后一个元素的随机访问迭代器。 |
rend() | 返回指向第一个元素之前一个位置的随机访问迭代器。 |
cbegin() | 和 begin() 功能相同,只不过在其基础上增加了 const 属性,不能用于修改元素。 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
size() | 返回容器中当前元素的数量,其值始终等于初始化 array 类的第二个模板参数 N。 |
max_size() | 返回容器可容纳元素的最大数量,其值始终等于初始化 array 类的第二个模板参数 N。 |
empty() | 判断容器是否为空,和通过 size()==0 的判断条件功能相同,但其效率可能更快。 |
at(n) | 返回容器中 n 位置处元素的引用,该函数自动检查 n 是否在有效的范围内,如果不是则抛出 out_of_range 异常。 |
front() | 返回容器中第一个元素的直接引用,该函数不适用于空的 array 容器。 |
back() | 返回容器中最后一个元素的直接应用,该函数同样不适用于空的 array 容器。 |
data() | 返回一个指向容器首个元素的指针。利用该指针,可实现复制容器中所有元素等类似功能。 |
fill(val) | 将 val 这个值赋值给容器中的每个元素。 |
array1.swap(array2) | 交换 array1 和 array2 容器中的所有元素,但前提是它们具有相同的长度和类型。 |
Vector (动态数组)
动态容量的数组,可动态延长/缩短数组长度
头文件
#include<vector>
创建动态数组
#include<vector>
void main()
{
// 普通创建
vector<int> v;
// 指定长度创建
vector<int> v(5);
// 指定数据创建
vector<int> v{1, 2, 3};
// 嵌套创建
vector< vector<int> > v;
}
遍历数组
#include<vector>
void main()
{
// 创建动态数组
vector<int> v{1, 2, 3};
// 遍历数组
for (auto i : v)
{
cout << i << endl;
}
}
成员函数 | 功能 |
---|---|
begin() | 返回指向容器中第一个元素的迭代器。 |
end() | 返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。 |
rbegin() | 返回指向最后一个元素的迭代器。 |
rend() | 返回指向第一个元素所在位置前一个位置的迭代器。 |
cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
size() | 返回实际元素个数。 |
max_size() | 返回元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。 |
resize() | 改变实际元素的个数。 |
capacity() | 返回当前容量。 |
empty() | 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。 |
reserve() | 增加容器的容量。 |
shrink _to_fit() | 将内存减少到等于当前元素实际所使用的大小。 |
operator[ ] | 重载了 [ ] 运算符,可以向访问数组中元素那样,通过下标即可访问甚至修改 vector 容器中的元素。 |
at() | 使用经过边界检查的索引访问元素。 |
front() | 返回第一个元素的引用。 |
back() | 返回最后一个元素的引用。 |
data() | 返回指向容器中第一个元素的指针。 |
assign() | 用新元素替换原有内容。 |
push_back() | 在序列的尾部添加一个元素。 |
pop_back() | 移出序列尾部的元素。 |
insert() | 在指定的位置插入一个或多个元素。 |
erase() | 移出一个元素或一段元素。 |
clear() | 移出所有的元素,容器大小变为 0。 |
swap() | 交换两个容器的所有元素。 |
emplace() | 在指定的位置直接生成一个元素。 |
emplace_back() | 在序列尾部生成一个元素。 |
Queue (单向队列)
只能在一端输入数据,另一端输出数据的列表
头文件
#include<queue>
创建单向队列
#include<queue>
void main()
{
// 创建单向队列
queue<int> q;
}
遍历队列
#include<queue>
void main()
{
// 创建单向队列
queue<int> q;
// 插入数据
q.push(1);
q.push(2);
q.push(3);
// 遍历数据,检测队列是否有数据
while (!q.empty())
{
// 打印队列首位数据
cout << q.front() << endl;
// 删除队列首位数据
q.pop();
}
}
成员函数 | 功能 |
---|---|
empty() | 如果 queue 中没有元素的话,返回 true。 |
size() | 返回 queue 中元素的个数。 |
front() | 返回 queue 中第一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。 |
back() | 返回 queue 中最后一个元素的引用。如果 queue 是常量,就返回一个常引用;如果 queue 为空,返回值是未定义的。 |
push(const T& obj) | 在 queue 的尾部添加一个元素的副本。这是通过调用底层容器的成员函数 push_back() 来完成的。 |
emplace() | 在 queue 的尾部直接添加一个元素。 |
push(T&& obj) | 以移动的方式在 queue 的尾部添加元素。这是通过调用底层容器的具有右值引用参数的成员函数 push_back() 来完成的。 |
pop() | 删除 queue 中的第一个元素。 |
swap(queue &other_queue) | 将两个 queue 容器适配器中的元素进行互换,需要注意的是,进行互换的 2 个 queue 容器适配器中存储的元素类型以及底层采用的基础容器类型,都必须相同。 |
Deque (双向队列)
在两端都可以进行输入输出数据的列表
头文件
#include<deque>
创建双向队列
#include<deque>
void main()
{
// 创建单向队列
deque<int> dq;
}
遍历队列
#include<deque>
void main()
{
// 创建双向队列
deque<int> d;
// 向队列头部添加数据
d.push_front(1);
d.push_front(2);
d.push_front(3);
// 向队列尾部添加数据
d.push_back(1);
d.push_back(2);
d.push_back(3);
// 遍历队列
for (int i = 0; i < d.size(); i++)
{
cout << d.at(i) << endl;
}
}
成员函数 | 功能 |
---|---|
begin() | 返回指向容器中第一个元素的迭代器。 |
end() | 返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。 |
rbegin() | 返回指向最后一个元素的迭代器。 |
rend() | 返回指向第一个元素所在位置前一个位置的迭代器。 |
cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。 |
size() | 返回实际元素个数。 |
max_size() | 返回容器所能容纳元素个数的最大值。这通常是一个很大的值,一般是 232-1,我们很少会用到这个函数。 |
resize() | 改变实际元素的个数。 |
empty() | 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。 |
shrink _to_fit() | 将内存减少到等于当前元素实际所使用的大小。 |
at() | 使用经过边界检查的索引访问元素。 |
front() | 返回第一个元素的引用。 |
back() | 返回最后一个元素的引用。 |
assign() | 用新元素替换原有内容。 |
push_back() | 在序列的尾部添加一个元素。 |
push_front() | 在序列的头部添加一个元素。 |
pop_back() | 移除容器尾部的元素。 |
pop_front() | 移除容器头部的元素。 |
insert() | 在指定的位置插入一个或多个元素。 |
erase() | 移除一个元素或一段元素。 |
clear() | 移出所有的元素,容器大小变为 0。 |
swap() | 交换两个容器的所有元素。 |
emplace() | 在指定的位置直接生成一个元素。 |
emplace_front() | 在容器头部生成一个元素。和 push_front() 的区别是,该函数直接在容器头部构造元素,省去了复制移动元素的过程。 |
emplace_back() | 在容器尾部生成一个元素。和 push_back() 的区别是,该函数直接在容器尾部构造元素,省去了复制移动元素的过程。 |
Stack (栈)
只能在一端进行输入和输出的列表
头文件
#include<stack>
创建栈
#include<stack>
void main()
{
// 创建单向队列
stack<int> s;
}
遍历栈
#include<stack>
void main()
{
// 创建栈
stack<int> s;
// 添加数据
s.push(1);
s.push(2);
s.push(3);
// 遍历栈,检测栈是否为空
while (!s.empty())
{
// 打印栈顶
cout << s.top() << endl;
// 删除栈顶
s.pop();
}
}
成员函数 | 功能 |
---|---|
empty() | 当 stack 栈中没有元素时,该成员函数返回 true;反之,返回 false。 |
size() | 返回 stack 栈中存储元素的个数。 |
top() | 返回一个栈顶元素的引用,类型为 T&。如果栈为空,程序会报错。 |
push(const T& val) | 先复制 val,再将 val 副本压入栈顶。这是通过调用底层容器的 push_back() 函数完成的。 |
push(T&& obj) | 以移动元素的方式将其压入栈顶。这是通过调用底层容器的有右值引用参数的 push_back() 函数完成的。 |
pop() | 弹出栈顶元素。 |
emplace(arg…) | arg… 可以是一个参数,也可以是多个参数,但它们都只用于构造一个对象,并在栈顶直接生成该对象,作为新的栈顶元素。 |
swap(stack & other_stack) | 将两个 stack 适配器中的元素进行互换,需要注意的是,进行互换的 2 个 stack 适配器中存储的元素类型以及底层采用的基础容器类型,都必须相同。 |
Map (字典)
map 容器存储的都是一一对应的数据,就像字典一样
头文件
#include<map>
创建字典
#include<map>
void main()
{
// 创建字典
map<string, int> s;
}
遍历字典
#include<map>
void main()
{
// 创建字典
map<string, int> m;
// 插入键值对
m.insert({"str", 1})
for (auto i = m.begin(); i != m.end(); i++)
{
// 打印键
cout << i->first << endl;
// 打印值
cout << i->second << endl;
}
}
成员函数 | 功能 |
---|---|
begin() | 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
find(key) | 在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
lower_bound(key) | 返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
upper_bound(key) | 返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
equal_range(key) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。 |
empty() | 若容器为空,则返回 true;否则 false。 |
size() | 返回当前 map 容器中存有键值对的个数。 |
max_size() | 返回 map 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。 |
operator[] | map 容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值,就可以向获取数组中元素那样,通过键直接获取对应的值。 |
at(key) | 找到 map 容器中 key 键对应的值,如果找不到,该函数会引发 out_of_range 异常。 |
insert() | 向 map 容器中插入键值对。 |
erase() | 删除 map 容器指定位置、指定键(key)值或者指定区域内的键值对。后续章节还会对该方法做重点讲解。 |
swap() | 交换 2 个 map 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。 |
clear() | 清空 map 容器中所有的键值对,即使 map 容器的 size() 为 0。 |
emplace() | 在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。 |
emplace_hint() | 在本质上和 emplace() 在 map 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。 |
count(key) | 在当前 map 容器中,查找键为 key 的键值对的个数并返回。注意,由于 map 容器中各键值对的键的值是唯一的,因此该函数的返回值最大为 1。 |
Set (集合)
一个不重复且排好序的列表
头文件
#include<set>
创建集合
#include<set>
void main()
{
// 创建集合
set<string, int> s;
}
遍历集合
#include<set>
void main()
{
// 初始赋值
set<int> s{1, 2, 3};
// 遍历集合
for (auto i = s.begin(); i != s.end(); i++)
{
cout << *i << endl;
}
}
成员函数 | 功能 |
---|---|
begin() | 返回指向容器中第一个(注意,是已排好序的第一个)元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的元素值。 |
find(val) | 在 set 容器中查找值为 val 的元素,如果成功找到,则返回指向该元素的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
lower_bound(val) | 返回一个指向当前 set 容器中第一个大于或等于 val 的元素的双向迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
upper_bound(val) | 返回一个指向当前 set 容器中第一个大于 val 的元素的迭代器。如果 set 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
equal_range(val) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的值为 val 的元素(set 容器中各个元素是唯一的,因此该范围最多包含一个元素)。 |
empty() | 若容器为空,则返回 true;否则 false。 |
size() | 返回当前 set 容器中存有元素的个数。 |
max_size() | 返回 set 容器所能容纳元素的最大个数,不同的操作系统,其返回值亦不相同。 |
insert() | 向 set 容器中插入元素。 |
erase() | 删除 set 容器中存储的元素。 |
swap() | 交换 2 个 set 容器中存储的所有元素。这意味着,操作的 2 个 set 容器的类型必须相同。 |
clear() | 清空 set 容器中所有的元素,即令 set 容器的 size() 为 0。 |
emplace() | 在当前 set 容器中的指定位置直接构造新元素。其效果和 insert() 一样,但效率更高。 |
emplace_hint() | 在本质上和 emplace() 在 set 容器中构造新元素的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示新元素生成位置的迭代器,并作为该方法的第一个参数。 |
count(val) | 在当前 set 容器中,查找值为 val 的元素的个数,并返回。注意,由于 set 容器中各元素的值是唯一的,因此该函数的返回值最大为 1。 |
内置算法
C++ 内置的算法,算法是解决问题的方法,常用的有排序、搜索、分组、比较,在开发中经常用到。
头文件
#include <algorithm>
排序算法
在编程中最常用的是排序算法,
函数名 | 用法 |
---|---|
sort (first, last) | 对 first-last 中的元素进行排序,默认进行升序排序 |
stable_sort (first, last) | 和 sort() 函数功能相似,不同之处在于该函数不会改变相同元素的相对位置 |
partial_sort (first, middle, last) | 从 first-last 中内筛选出 muddle-first 中最小的元素并排序存放在 first-middle 中 |
partial_sort_copy (first, last, result_first, result_last) | 与 partial_sort 相似,区别在于会先拷贝容器再排序 |
is_sorted (first, last) | 检测 first-last 中的元素是否已经排好序,默认检测是否按升序排序。 |
is_sorted_until (first, last) | 和 is_sorted() 函数功能类似,区别在于会返回第一个不符合的位置 |
void nth_element (first, nth, last) | 找到 [first, last) 范围内按照排序规则(默认按照升序排序)应该位于第 nth 个位置处的元素,并将其放置到此位置。同时使该位置左侧的所有元素都比其存放的元素小,该位置右侧的所有元素都比其存放的元素大。 |
查找算法
C++ 内置的常用函数,用来搜索数组、Array、Vector、Deque 的元素
函数名 | 用法 |
---|---|
find(first, last, val) | 查找 first-last 之间 val 的位置 |
find_end(first1, last1, first2, last2) | 查找 first2-last2 在 first1-last1 最后出现的位置 |
find_if(first, last, function) | 根据 function 的规则查找 first-last 中的元素 |
分组算法
可根据用户自定义的筛选规则,重新排列指定区域内存储的数据
函数名 | 用法 |
---|---|
partition(first, last, function) | 将符合 function 规则的元素排在前面,但不排序 |
stable_partition(first, last, function) | 将符合 function 规则的元素排在前面并排序 |
partition_copy((first, last, result_true, result_false, function)) | 根据 function 的规则查找 first-last,将符合 function 规则的放置在 result_true 中,不符合的放置在 result_false 中 |
partition_point(first, last, function) | 根据 function 的规则查找 first-last 中第一个不符合规则的元素位置 |
其它关键词
asm
在 C++ 内部嵌入汇编指令
int main()
{
asm("汇编指令")
}
inline
用来表示内联函数,提升函数被多次调用时的效率,省去调用函数的开销,一般用于代码较少的函数
inline void sum(int a, int b)
{
return a + b;
}
explicit
explicit 只对类的构造函数有效,用来防止构造函数进行隐式类型转换
class ClassA
{
public:
int _a;
explicit ClassA(int a){ _a = a; }
};
int main()
{
ClassA a(9);
// ClassA b = 9; 报错,不允许隐式类型转换
}
mutable
用来表示可变数据,一般用于 const 对象,定义 mutable 后,可以在 const 中修改数据
class ClassA
{
public:
mutable int a;
};
int main()
{
const ClassA A;
A.a = 10;
}
namespace
用来定义命名空间,一般用来避免工程较大时产生的名称冲突,只能在全局中定义
namespace ns
{
int a;
}
int main()
{
// 使用命名空间
ns::a = 1;
// 引用命名空间,在接下来可以省略命名空间直接调用数据
using namespace ns;
a = 1;
}
register
用来修饰变量,将变量直接寄存在 CPU 中,提升运行效率
int main()
{
register int a;
}
volatile
用来修饰对象,声明后会让编译器必须每次重新读取这个对象,不会假定他的值
如果不声明,这个对象假如被系统、硬件、汇编改变,编译器还是会按照他“假定”的值来计算
一般涉及硬件编程时使用
int main()
{
volatile int a;
}
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于