C语言中文网C++学习笔记

目录

类也是一种构造类型,不但可以是变量,还可以是函数。

类定义出来的变量也有特定的称呼,叫做“对象”

class Student stu1; //通过类来定义变量,即创建对象,也可以省略关键字class
1.//为类的成员变量赋值
2.stu1.name = “小明”;
3.stu1.age = 15;
4.stu1.score = 92.5f;
5.//调用类的成员函数
6.stu1.say();

链接: link

解决合作开发时的命名冲突问题,C++ 引入了命名空间(Namespace)

类、函数、typedef、#define 等都可以出现在命名空间中
1.namespace Li{ //小李的变量定义
2.FILE fp = NULL;
3.}
4.namespace Han{ //小韩的变量定义
5.FILE fp = NULL
6.}
7.Li::fp = fopen(“one.txt”, “r”); //使用小李定义的变量 fp,::是一个新符号,称为域解析 操作符,在C++中用来指明要使用的命名空间。
8.Han::fp = fopen(“two.txt”, “rb+”); //使用小韩定义的变量 fp
链接: link

1.#include
2.#include
3.//声明命名空间std
4.using namespace std;
5.cout<<“C语言中文网”<<endl;
链接: link

在C语言中,动态分配内存用 malloc() 函数,释放内存用 free() 函数。

在C++中,这两个函数仍然可以使用,但是C++又新增了两个关键字,new 和 delete:new 用来动态分配内存,delete 用来释放内存
1.int *p = new int[10]; //分配10个int型的内存空间
2.delete[] p;
链接: link

函数调用是有时间和空间开销的。

程序在执行一个函数之前需要做一些准备工作,要将实参、局部变量、返回地址以及若干寄存器都压入栈中,然后才能执行函数体中的代码;函数体中的代码执行完毕后还要清理现场,将之前压入栈中的数据都出栈,才能接着执行函数调用位置以后的代码。关于函数调用的细节,我们已经在《C语言内存精讲》一章中的《一个函数在栈上到底是怎样的》《用一个实例来深入剖析函数进栈出栈的过程》两节中讲到。
链接: link

C++ 允许多个函数拥有相同的名字,只要它们的参数列表不同就可以,这就是函数的重载(Function Overloading)。

C++代码在编译时会根据参数列表对函数进行重命名,例如void Swap(int a, int b)会被重命名为_Swap_int_int
链接: link

栈 堆 指针

在栈上创建出来的对象都有一个名字,比如 stu,使用指针指向它不是必须的。使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数。

栈内存是程序自动管理的,不能使用 delete 删除在栈上创建的对象;堆内存由程序员管理,对象使用完毕后可以通过 delete 删除。在实际开发中,new 和 delete 往往成对出现,以保证及时删除不再使用的对象,防止无用内存堆积。
有了对象指针后,可以通过箭头->来访问对象的成员变量和成员函数
在栈上创建出来对象以后,使用点号.来访问成员变量和成员函数
链接: link

C++类成员的访问权限以及类的封装

这种将成员变量声明为 private、将部分成员函数声明为 public 的做法体现了类的封装性。所谓封装,是指尽量隐藏类的内部实现,只向用户提供有用的成员函数。

链接: link

构造函数在实际开发中会大量使用,它往往用来做一些初始化工作,例如对成员变量赋值、预先打开文件等。

  1. class Student{
    1.public:
    2.//声明构造函数
    3.Student(char *name, int age, float score);

2.//定义构造函数
3.Student::Student(char *name, int age, float score){
4.m_name = name;
5.m_age = age;
6.m_score = score;
7.}

8.int main(){
9.//创建对象时向构造函数传参
10.Student stu(“小明”, 15, 92.5f);
链接: link

C++构造函数初始化列表
1.//采用初始化列表
2.Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
3.//TODO:
4.}
链接: link

用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数

不懂
通过 delete 删除时才会调用析构函数
1.public:
2.Demo(string s);
3.~Demo();
链接: link

this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,通过它可以访问当前对象的所有成员。
链接: link

在C++中,我们可以使用静态成员变量来实现多个对象共享数据的目标。静态成员变量是一种特殊的成员变量,它被关键字static修饰

1.public:
2.static int m_total; //静态成员变量
static 成员变量属于类,不属于某个具体的对象,即使创建多个对象,也只为 m_total 分配一份内存,所有对象使用的都是这份内存中的数据。当某个对象修改了 m_total,也会影响到其他对象。
http://c.biancheng.net/view/2227.html

const
http://c.biancheng.net/view/2230.html

insert() 函数可以在 string 字符串中指定的位置插入另一个字符串
erase() 函数可以删除 string 中的一个子字符串。
substr() 函数用于从 string 字符串中提取子字符串
find() 函数用于在 string 字符串中查找子字符串出现的位置
http://c.biancheng.net/view/2236.html

C++ ,比指针更加便捷的传递聚合类型数据的方式,那就是引用(Reference)。

引用可以看做是数据的一个别名,通过这个别名能够找到这份数据
type &name = data;// type 是被引用的数据的类型,name 是引用的名称,data 是被引用的数据
C++引用作为函数参数
1.//按引用传参
2.void swap3(int &r1, int &r2) {
3.int temp = r1;
4.r1 = r2;
5.r2 = temp;
6.}
从以上代码的编写中可以发现,按引用传参在使用形式上比指针更加直观
引用和指针并没有本质上的区别,引用仅仅是对指针进行了简单封装
http://c.biancheng.net/view/2251.html

继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程

在C++中,派生(Derive)和继承是一个概念,只是站的角度不同。继承是儿子接收父亲的产业,派生是父亲把产业传承给儿子。
//基类 Pelple
class People{

//派生类 Student
class Student: public People{
http://c.biancheng.net/view/2264.html

多继承
在前面的例子中,派生类都只有一个基类,称为单继承(Single Inheritance)。除此之外,C++也支持多继承(Multiple Inheritance),即一个派生类可以有两个或多个基类。
class D: public A, private B, protected C{
//类D新增加的成员
}
为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。

在继承方式前面加上 virtual 关键字就是虚继承
1.//间接基类A
2.class A{
3.protected:
4. int m_a;
5.};
6.//直接基类B
7.class B: virtual public A{ //虚继承
8.protected:
9. int m_b;
10.};
11.//直接基类C
12.class C: virtual public A{ //虚继承
13.protected:
14. int m_c;
15.};
16.//派生类D
17.class D: public B, public C{
18.public:
19. void seta(int a){ m_a = a; } //正确
20. void setb(int b){ m_b = b; } //正确
21. void setc(int c){ m_c = c; } //正确
22. void setd(int d){ m_d = d; } //正确
23.private:
24. int m_d;
25.};
这段代码使用虚继承重新实现了上图所示的菱形继承,这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了
不提倡在程序中使用多继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多继承,能用单一继承解决的问题就不要使用多继承。也正是由于这个原因,C++ 之后的很多面向对象的编程语言,例如 Java、C#、PHP 等,都不支持多继承。
http://c.biancheng.net/view/2280.html

多态

面向对象程序设计语言有封装、继承和多态三种机制,这三种机制能够有效提高程序的可读性、可扩充性和可重用性。
封装:指的是将对象实现细节隐藏起来,然后通过一些公用方法来暴露该对象的功能;
继承:继承是面向对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类,将直接获得父类的属性和方法;
多态:多态指的是子类对象可以直接赋给父类变量,但运行时依然表现子类的行为特征,这意味着同一个类型的对象在执行同一个方法时,可能表现出多种行为特征。
多态(polymorphism)”指的是同一名字的事物可以完成不同的功能。多态可以分为编译时的多态和运行时的多态。前者主要是指函数的重载(包括运算符的重载)、对重载函数的调用,在编译时就能根据实参确定应该调用哪个函数,因此叫编译时的多态;而后者则和继承、虚函数等概念有关,是本章要讲述的内容。本教程后面提及的多态都是指运行时的多态。
通过基类指针只能访问派生类的成员变量,但是不能访问派生类的成员函数。

为了消除这种尴尬,让基类指针能够访问派生类的成员函数,C++ 增加了虚函数(Virtual Function)。使用虚函数非常简单,只需要在函数声明前面增加 virtual 关键字。
有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)。
上面的代码中,同样是p->display();这条语句,当 p 指向不同的对象时,它执行的操作是不一样的。同一条语句可以执行不同的操作,看起来有不同表现方式,这就是多态。
多态是面向对象编程的主要特征之一,C++中虚函数的唯一用处就是构成多态。
C++提供多态的目的是:可以通过基类指针对所有派生类(包括直接派生和间接派生)的成员变量和成员函数进行“全方位”的访问,尤其是成员函数。如果没有多态,我们只能访问成员变量。
多态在小项目中鲜有有用武之地。
对于具有复杂继承关系的大中型程序,多态可以增加其灵活性,让代码更具有表现力。
接下来的例子中,我们假设你正在玩一款军事游戏,敌人突然发动了地面战争,于是你命令陆军、空军及其所有现役装备进入作战状态。具体的代码如下所示:

http://c.biancheng.net/view/2294.html

C++ 虚函数对于多态具有决定性的作用,有虚函数才能构成多态

多态是指通过基类的指针既可以访问基类的成员,也可以访问派生类的成员。
下面是构成多态的条件:
必须存在继承关系;
继承关系中必须有同名的虚函数,并且它们是覆盖关系(函数原型相同)。
存在基类的指针,通过该指针调用虚函数。
什么时候声明虚函数
首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。

http://c.biancheng.net/view/2296.html

纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。

最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。
包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。
抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。.

http://c.biancheng.net/view/2299.html

typeid 运算符用来获取一个表达式的类型信息。类型信息对于编程语言非常重要,它描述了数据的各种属性:

对于基本类型(int、float 等C++内置类型)的数据,类型信息所包含的内容比较简单,主要是指数据的类型。
对于类类型的数据(也就是对象),类型信息是指对象所属的类、所包含的成员、所在的继承关系等。
http://c.biancheng.net/view/2301.html

运算符重载的格式为:

没看
返回值类型 operator 运算符名称 (形参表列){
//TODO:
}

http://c.biancheng.net/view/2306.html

运算符重载的实质是将运算符重载为一个函数,使用运算符的表达式就被解释为对重载函数的调用。
http://c.biancheng.net/view/248.html

泛型程序设计(generic programming)是一种算法在实现时不指定具体要操作的数据的类型的程序设计方法。所谓“泛型”,指的是算法只要实现一遍,就能适用于多种数据类型。泛型程序设计方法的优势在于能够减少重复代码的编写。

泛型程序设计的概念最早出现于 1983 年的 Ada 语言,其最成功的应用就是 C++ 的标准模板库(STL)。也可以说,泛型程序设计就是大量编写模板、使用模板的程序设计。泛型程序设计在 C++ 中的重要性和带来的好处不亚于面向对象的特性。
在 C++ 中,模板分为函数模板和类模板两种。熟练的 C++ 程序员,在编写函数时都会考虑能否将其写成函数模板,编写类时都会考虑能否将其写成类模板,以便实现重用。

函数模板

1.#include
2.using namespace std;
3.
4.template void Swap(T &a, T &b){交换两个值, 引用让编码更加漂亮
5.T temp = a;
6.a = b;
7.b = temp;
8.}
9.int main(){
10.//交换 int 变量的值
11.int n1 = 100, n2 = 200;
12.Swap(n1, n2);
13.cout<<n1<<", "<<n2<<endl;
http://c.biancheng.net/view/2317.html

类模板

1.#include
2.using namespace std;
3.
4.template //这里不能有分号
5.class Point{
6.public:
7.Point(T1 x): m_x(x) { }
8.public:
9.T1 getX() const; //获取x坐标
10.void setX(T1 x); //设置x坐标
11.private:
12.T1 m_x; //x坐标
13.};
14.int main(){
15.Point p1(10);
16.cout<<“x=”<<p1.getX()<<endl;
http://c.biancheng.net/view/2318.html

强类型语言在定义变量时需要显式地指明数据类型,并且一旦为变量指明了某种数据类型,该变量以后就不能赋予其他类型的数据了,除非经过强制类型转换或隐式类型转换。典型的强类型语言有 C/C++、Java、C# 等。

int a = 100; //不转换
1.a = 12.34; //隐式转换(直接舍去小数部分,得到12)

典型的弱类型语言有 JavaScript、Python、PHP、Ruby、Shell、Perl 等。

JavaScript 中使用变量:
1.var a = 100; //赋给整数
2.a = 12.34; //赋给小数
弱类型语言往往是一边执行一边编译,这样可以根据上下文(可以理解为当前的执行环境)推导出很多有用信息,让编译更加高效。我们将这种一边执行一边编译的语言称为解释型语言,而将传统的先编译后执行的语言称为编译型语言。
强类型语言较为严谨,在编译时就能发现很多错误,适合开发大型的、系统级的、工业级的项目;而弱类型语言较为灵活,编码效率高,部署容易,学习成本低,在 Web 开发中大显身手。另外,强类型语言的 IDE 一般都比较强大,代码感知能力好,提示信息丰富;而弱类型语言一般都是在编辑器中直接书写代码。
PHP 不需要使用模板就可以处理多种类型的数据,它天生对类型就不敏感。C++ 就不一样了,它是强类型的,比较“死板”,所以后来 C++ 开始支持模板了,主要就是为了弥补强类型语言“不够灵活”的缺点。

模板所支持的类型是宽泛的,没有限制的,我们可以使用任意类型来替换,这种编程方式称为泛型编程(Generic Programming)。相应地,可以将参数 T 看做是一个泛型,而将 int、float、string 等看做是一种具体的类型。除了 C++,Java、C#、Pascal(Delphi)也都支持泛型编程。

C++ 模板也是被迫推出的,最直接的动力来源于对数据结构的封装。数据结构关注的是数据的存储,以及存储后如何进行增加、删除、修改和查询操作,它是一门基础性的学科,在实际开发中有着非常广泛的应用。C++ 开发者们希望为线性表、链表、图、树等常见的数据结构都定义一个类,并把它们加入到标准库中,这样以后程序员就不用重复造轮子了,直接拿来使用即可。

但是这个时候遇到了一个无法解决的问题,就是数据结构中每份数据的类型无法提前预测。以链表为例,它的每个节点可以用来存储小数、整数、字符串等,也可以用来存储一名学生、教师、司机等,还可以直接存储二进制数据,这些都是可以的,没有任何限制。而 C++ 又是强类型的,数据的种类受到了严格的限制,这种矛盾是无法调和的。

要想解决这个问题,C++ 必须推陈出新,跳出现有规则的限制,开发新的技术,于是模板就诞生了。模板虽然不是 C++ 的首创,但是却在 C++ 中大放异彩,后来也被 Java、C# 等其他强类型语言采用。

C++ 模板有着复杂的语法,可不仅仅是前面两节讲到的那么简单,它的话题可以写一本书。C++ 模板也非常重要,整个标准库几乎都是使用模板来开发的,STL 更是经典之作。
STL(Standard Template Library,标准模板库)就是 C++ 对数据结构进行封装后的称呼。

http://c.biancheng.net/view/2319.html

C++异常处理入门

程序的错误大致可以分为三种,分别是语法错误、逻辑错误和运行时错误:

  1. 语法错误在编译和链接阶段就能发现,只有 100% 符合语法规则的代码才能生成可执行程序。语法错误是最容易发现、最容易定位、最容易排除的错误,程序员最不需要担心的就是这种错误。

  2. 逻辑错误是说我们编写的代码思路有问题,不能够达到最终的目标,这种错误可以通过调试来解决。

  3. 运行时错误是指程序在运行期间发生的错误,例如除数为 0、内存分配失败、数组越界、文件不存在等。C++ 异常(Exception)机制就是为解决运行时错误而引入的。
    捕获异常
    我们可以借助 C++ 异常机制来捕获上面的异常,避免程序崩溃。捕获异常的语法为:
    try{
    // 可能抛出异常的语句
    }catch(exceptionType variable){
    // 处理异常的语句
    }
    这就好比,catch 告诉 try:你去检测一下程序有没有错误,有错误的话就告诉我,我来处理,没有的话就不要理我!
    http://c.biancheng.net/view/2330.html

C++ 异常处理的流程,具体为:
抛出(Throw)–> 检测(Try) --> 捕获(Catch)
异常必须显式地抛出,才能被检测和捕获到;如果没有显式的抛出,即使有异常也检测不到。

在 C++ 中,我们使用 throw 关键字来显式地抛出异常,它的用法为:
throw exceptionData;
http://c.biancheng.net/view/2332.html

对于简单的类,默认拷贝构造函数一般是够用的,我们也没有必要再显式地定义一个功能类似的拷贝构造函数。但是当类持有其它资源时,如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认拷贝构造函数就不能拷贝这些资源,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据,这点我们将在《C++深拷贝和浅拷贝》一节中深入讲解。

1.class Student{
2.public:
3.Student(string name = “”, int age = 0, float score = 0.0f); //普通构造函数
4.Student(const Student &stu); //拷贝构造函数(声明)
5.//拷贝构造函数(定义)
6.Student::Student(const Student &stu){
7.this->m_name = stu.m_name;
8.this->m_age = stu.m_age;
9.this->m_score = stu.m_score;
10.
11.cout<<“Copy constructor was called.”<<endl;
12.}
1.int main(){
2.Student stu1(“小明”, 16, 90.5);
3.Student stu2 = stu1; //调用拷贝构造函数
4.Student stu3(stu1); //调用拷贝构造函数
http://c.biancheng.net/view/2334.html

程序中常用的 cin 和 cout,分别用于从键盘输入数据和向屏幕输出数据(简称为标准 I/O)。除此之外,程序还可以从文件中读入数据,以及向文件中写入数据(简称为文件 I/O)。

数据输入和输出的过程也是数据传输的过程。数据像水一样从一个地方流动到另一个地方,因此,在 C++ 中将此过程称为 “流(stream)”。
1.#include
2.using namespace std;
3.int main()
4.{
5. int x,y;
6. cin >> x >> y;
7. freopen(“test.txt”, “w”, stdout); //将标准输出重定向到 test.txt文件
8. if( y == 0 ) //除数为0则输出错误信息
9. cerr << “error.” << endl;
10. else
11. cout << x /y ;
12. return 0;
13.}
第 7 行的 freopen 是一个标准库函数,第二个参数 w 代表写模式,第三个参数代表标准输出。该语句的作用是将标准输出重定向为 test.txt 文件。

重定向之后,所有对 cout 的输出都不再出现在屏幕上,而是出现在 test.txt 文件中。
cin 也是可以被重定向的。如果在程序中加入
1.freopen(“input.dat”, “r”, stdin);
第二个参数 r 代表读入方式,第三个参数 stdin 代表标准输入。执行此语句后,cin 就不再从键盘读入数据,而是从 input.dat 文件中读人数据,input.dat 文件中有什么,就相当于从键盘输入了什么。
http://c.biancheng.net/view/272.html

C++ cout格式化输出(输出格式)
流操作算子的使用方法
使用这些算子的方法是将算子用 << 和 cout 连用。例如:十六进制
1.cout << hex << 12 << “,” << 24;
这条语句的作用是指定以十六进制形式输出后面两个数,因此输出结果是:
c, 18
http://c.biancheng.net/view/275.html

输出单个字符 a。
cout.put(‘a’);
http://c.biancheng.net/view/3750.html

cin 判断文件读取结束
1.#include
2.using namespace std;
3.int main()
4.{
5.int c;
6.while ((c = cin.get()) != EOF)// 此函数从输入流中读入一个字符,EOF 是 End of File 的缩写
7.cout.put©;
8.return 0;
9.}
http://c.biancheng.net/view/278.html

cin.getline():C++读入一行字符串(整行数据)
http://c.biancheng.net/view/279.html

cin.ignore():C++跳过(忽略)指定字符
http://c.biancheng.net/view/280.html

C++文本文件的读取和写入
将文件 in.txt 中的整数排序后输出到 out.txt
ifstream srcFile(“in.txt”,ios::in); //以文本模式打开in.txt备读
ofstream destFile(“out.txt”,ios::out); //以文本模式打开out.txt备写
1.while(srcFile >> x) //可以像用cin那样用ifstream对象
2.a[total++] = x;
3.
4.for(int i = 0;i < total; ++i)
5.destFile << a[i] << " "; //可以像用cout那样用ofstream对象

http://c.biancheng.net/view/299.html

C++二进制文件的读取和写入(精华版)
1.ofstream outFile(“students.dat”, ios::out | ios::binary);
文件存放于磁盘中,磁盘的访问速度远远低于内存。如果每次读一个字节或写一个字节都要访问磁盘,那么文件的读写速度就会慢得不可忍受。因此,操作系统在接收到读文件的请求时,哪怕只要读一个字节,也会把一片数据(通常至少是 512 个字节,因为磁盘的一个扇区是 512 B)都读取到一个操作系统自行管理的内存缓冲区中,当要读下一个字节时,就不需要访问磁盘,直接从该缓冲区中读取就可以了。

操作系统在接收到写文件的请求时,也是先把要写入的数据在一个内存缓冲区中保存起来,等缓冲区满后,再将缓冲区的内容全部写入磁盘。关闭文件的操作就能确保内存缓冲区中的数据被写入磁盘。

尽管如此,要连续读写文件时,像 mycopy 程序那样一个字节一个字节地读写,还是不如一次读写一片内存区域快。每次读写的字节数最好是 512 的整数倍。
http://c.biancheng.net/view/302.html

在读写文件时,有时希望直接跳到文件中的某处开始读写,这就需要先将文件的读写指针指向该处,然后再进行读写。
所谓“位置”,就是指距离文件开头有多少个字节。文件开头的位置是 0。
ifstream 类和 fstream 类有 seekg 成员函数,可以设置文件读指针的位置;
ofstream 类和 fstream 类有 seekp 成员函数,可以设置文件写指针的位置。
ifstream 类和 fstream 类还有 tellg 成员函数,能够返回文件读指针的位置;
ofstream 类和 fstream 类还有 tellp 成员函数,能够返回文件写指针的位置。
http://c.biancheng.net/view/309.html

C++ 语言的核心优势之一就是便于软件的重用。C++ 中有两个方面体现重用:

一是面向对象的继承和多态机制;

二是通过模板的概念实现了对泛型程序设计的支持。

C++ 的标准模板库(Standard Template Library,STL)是泛型程序设计最成功应用的实例。STL 是一些常用数据结构(如链表、可变长数组、排序二叉树)和算法(如排序、查找)的模板的集合
容器(container)用于存放数据的类模板。可变长数组、链表、平衡二叉树等数据结构在 STL 中都被实现为容器。
顺序容器有以下三种:可变长动态数组 vector、双端队列 deque、双向链表 list。它们之所以被称为顺序容器,是因为元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置(尾部、头部或中间某处)插入,元素就会位于什么位置。
关联容器有以下四种:set、multiset、map、multimap。关联容器内的元素是排序的。插入元素时,容器会按一定的排序规则将元素放到适当的位置上,因此插入元素时不能指定位置。
默认情况下,关联容器中的元素是从小到大排序(或按关键字从小到大排序)的,而且用<运算符比较元素或关键字大小。因为是排好序的,所以关联容器在查找时具有非常好的性能。
容器都是类模板。它们实例化后就成为容器类。用容器类定义的对象称为容器对象。
例如,vector是一个容器类的名字,vector a;就定义了一个容器对象 a,a 代表一个长度可变的数组,数组中的每个元素都是 int 类型的变量
所有容器都有以下两个成员函数:
int size():返回容器对象中元素的个数。
bool empty():判断容器对象是否为空。

顺序容器和关联容器还有以下成员函数:
begin():返回指向容器中第一个元素的迭代器。
end():返回指向容器中最后一个元素后面的位置的迭代器。
rbegin():返回指向容器中最后一个元素的反向迭代器。
rend():返回指向容器中第一个元素前面的位置的反向迭代器。
erase(…):从容器中删除一个或几个元素。该函数参数较复杂,此处省略。
clear():从容器中删除所有元素。

如果一个容器是空的,则 begin() 和 end() 的返回值相等,rbegin() 和 rend() 的返回值也相等。

顺序容器还有以下常用成员函数:
front():返回容器中第一个元素的引用。
back():返回容器中最后一个元素的引用。
push_back():在容器末尾增加新元素。
pop_back():删除容器末尾的元素。
insert(…):插入一个或多个元素。该函数参数较复杂,此处省略。
http://c.biancheng.net/view/331.html

要访问顺序容器和关联容器中的元素,需要通过“迭代器(iterator)”进行。迭代器是一个变量,相当于容器和操纵容器的算法之间的中介。迭代器可以指向容器中的某个元素,通过迭代器就可以读写它指向的元素。从这一点上看,迭代器和指针类似。

  1. 正向迭代器,定义方法如下:
    容器类名::iterator 迭代器名;
  2. 常量正向迭代器,定义方法如下:
    容器类名::const_iterator 迭代器名;
  3. 反向迭代器,定义方法如下:
    容器类名::reverse_iterator 迭代器名;
  4. 常量反向迭代器,定义方法如下:
    容器类名::const_reverse_iterator 迭代器名;
    迭代器用法示例
    通过迭代器可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。通过非常量迭代器还能修改其指向的元素。
    下面的程序演示了如何通过迭代器遍历一个 vector 容器中的所有元素。
    #include
    #include
    1.using namespace std;
    2.int main()
    3.{
  1. vector v; //v是存放int类型变量的可变长数组,开始时没有元素
  2. for (int n = 0; n<5; ++n)
  3.    v.push_back(n);  //push_back成员函数在vector容器尾部添加一个元素
    
  4. vector::iterator i; //定义正向迭代器
  5. for (i = v.begin(); i != v.end(); ++i) { //用迭代器遍历容器,对正向迭代器进行++操作时,迭代器会指向容器中的后一个元素;
  6.    cout << *i << " ";  //*i 就是迭代器i指向的元素
    
  7.    *i *= 2;  //每个元素变为原来的2倍
    
  8. }
  9. cout << endl;
  10. //用反向迭代器遍历容器
  11. for (vector::reverse_iterator j = v.rbegin(); j != v.rend(); ++j) 对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素
  12.    cout << *j << " ";
    
  13. return 0;
    17.}
    程序的输出结果是:
    0 1 2 3 4
    8 6 4 2 0
    后置++要多生成一个局部对象 tmp,因此执行速度比前置的慢。同理,迭代器是一个对象,STL 在重载迭代器的++运算符时,后置形式也比前置形式慢。在次数很多的循环中,++i和i++可能就会造成运行时间上可观的差别了。因此,本教程在前面特别提到,对循环控制变量i,要养成写++i、不写i++的习惯。

STL算法详解

STL 提供能在各种容器中通用的算法(大约有70种),如插入、删除、查找、排序等。算法就是函数模板。算法通过迭代器来操纵容器中的元素。
算法可以处理容器,也可以处理普通的数组。
STL 中的大部分常用算法都在头文件 algorithm 中定义。此外,头文件 numeric 中也有一些算法。查找
vector v;
p = find(v.begin(),v.end(),3); //在v中查找3
排序
1.int a[4] = {3, 4, 2, 1};
2.sort(a, a+4);
http://c.biancheng.net/view/343.html

C++ vector,STL vector(可变长的动态数组)详解
vector 是顺序容器的一种。vector 是可变长的动态数组,支持随机访问迭代器,所有 STL 算法都能对 vector 进行操作。要使用 vector,需要包含头文件 vector。

vector 有很多成员函数,常用的如表 1 所示。

表1:vector中常用的成员函数
成员函数 作 用
vector() 无参构造函数,将容器初始化为空
vector(int n) 将容器初始化为有 n 个元素
vector(int n, const T & val) 假定元素的类型是 T,此构造函数将容器初始化为有 n 个元素,每 个元素的值都是 val
vector(iterator first, iterator last) first 和 last 可以是其他容器的迭代器。一般来说,本构造函数初始化的结果就是将 vector 容器的内容变成与其他容器上的区间 [first, last) —致
void clear() 删除所有元素
bool empty() 判断容器是否为空
void pop_back() 删除容器末尾的元素
void push_back( const T & val) 将 val 添加到容器末尾
int size() 返回容器中元素的个数
T & front() 返回容器中第一个元素的引用
T & back() 返回容器中最后一个元素的引用
iterator insert(iterator i, const T & val) 将 val 插入迭代器 i 指向的位置,返回 i
iterator insert( iterator i, iterator first, iterator last) 将其他容器上的区间 [first, last) 中的元素插入迭代器 i 指向的位置
iterator erase(iterator i) 删除迭代器 i 指向的元素,返回值是被删元素后面的元素的迭代器
iterator erase(iterator first, iterator last) 删除容器中的区间 [first, last)
void swap( vector & v) 将容器自身的内容和另一个同类型的容器 v 互换

下面的程序演示了 vector 的基本用法。
#include
#include //使用vector需要包含此头文件
using namespace std;
template
void PrintVector(const vector & v)
{ //用于输出vector容器的全部元素的函数模板
typename vector ::const_iterator i;
//typename 用来说明 vector ::const_iterator 是一个类型,在 Visual Studio 中不写也可以
for (i = v.begin(); i != v.end(); ++i)
cout << *i << " ";
cout << endl;
}
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
vector v(a, a + 5); //将数组a的内容放入v
cout << "1) " << v.end() - v.begin() << endl; //两个随机迭代器可以相减,输出:1)5
cout << “2)”; PrintVector(v); //输出:2)1 2 3 4 5
v.insert(v.begin() + 2, 13); //在 begin()+2 位置插人 13
cout << “3)”; PrintVector(v); //输出:3)1 2 13 3 4 5
v.erase(v.begin() + 2); //删除位于 begin()+2 位置的元素
cout << “4)”; PrintVector(v); //输出:4)1 2 3 4 5
vector v2(4, 100); //v2 有 4 个元素,都是 100
v2.insert(v2.begin(), v.begin() + 1, v.begin() + 3); //将v的一段插入v2开头
cout << “5)v2:”; PrintVector(v2); //输出:5)v2:2 3 100 100 100 100
v.erase(v.begin() + 1, v.begin() + 3); //删除 v 上的一个区间,即 [2,3)
cout << “6)”; PrintVector(v); //输出:6)1 4 5
return 0;
}

vector 还可以嵌套以形成可变长的二维数组。
http://c.biancheng.net/view/348.html

list 是顺序容器的一种。list 是一个双向链表。使用 list 需要包含头文件 list。双向链表的每个元素中都有一个指针指向后一个元素,也有一个指针指向前一个元素,如图1所示。

list 的构造函数和许多成员函数的用法都与 vector 类似,此处不再列举。除了顺序容器都有的成员函数外,list 容器还独有如表 1 所示的成员函数(此表不包含全部成员函数,且有些函数的参数较为复杂,表中只列出函数名)。

表1:list 的成员函数
成员函数或成员函数模板 作 用
void push_front(const T & val) 将 val 插入链表最前面
void pop_front() 删除链表最前面的元素
void sort() 将链表从小到大排序
void remove (const T & val) 删除和 val 相等的元素
remove_if 删除符合某种条件的元素
void unique() 删除所有和前一个元素相等的元素
void merge(list & x) 将链表 x 合并进来并清空 x。要求链表自身和 x 都是有序的
void splice(iterator i, list & x, iterator first, iterator last) 在位置 i 前面插入链表 x 中的区间 [first, last),并在链表 x 中删除该区间。链表自身和链表 x 可以是同一个链表,只要 i 不在 [first, last) 中即可

【实例】用 list 解决约瑟夫问题。

约瑟夫问题是:有 n 只猴子,按顺时针方向围成一圈选大王(编号为 1~n),从第 1 号开始报数,一直数到 m,数到 m 的猴子退到圈外,剩下的猴子再接着从 1 开始报数。就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王。编程求输入 n、m 后,输出最后猴王的编号。
#include
#include
using namespace std;
int main()
{
list monkeys;
int n, m;
while (true) {
cin >> n >> m;
if (n == 0 && m == 0)
break;
monkeys.clear(); //清空list容器
for (int i = 1; i <= n; ++i) //将猴子的编号放入list
monkeys.push_back(i);
list::iterator it = monkeys.begin();
while (monkeys.size() > 1) { //只要还有不止一只猴子,就要找一只猴子让其出列
for (int i = 1; i < m; ++i) { //报数
++it;
if (it == monkeys.end())
it = monkeys.begin();
}
it = monkeys.erase(it); //删除元素后,迭代器失效,
//要重新让迭代器指向被删元素的后面
if (it == monkeys.end())
it = monkeys.begin();
}
cout << monkeys.front() << endl; //front返回第一个元素的引用
}
return 0;
}

这个程序也可以用 vector 实现,但是执行速度要慢很多。因为 vector 的 erase 操作牵涉元素的移动,不能在常数时间内完成,所花费的时间和容器中的元素个数有关;而 list 的 erase 操作只是修改几个指针而已,可以在常数时间内完成

http://c.biancheng.net/view/351.html

deque 也是顺序容器的一种,同时也是一个可变长数组。要使用 deque,需要包含头文件 deque。所有适用于 vector 的操作都适用于 deque。
它相比于 vector 的优点是,vector 在头部删除或添加元素的速度很慢,在尾部添加元素的性能较好,而 deque 在头尾增删元素都具有较好的性能

它有两种 vector 没有的成员函数:
void push_front (const T & val); //将 val 插入容器的头部
void pop_front(); //删除容器头部的元素

http://c.biancheng.net/view/353.html

C++函数对象详解(附带实例)

如果一个类将()运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象。函数对象是一个对象,但是使用的形式看起来像函数调用,实际上也执行了函数调用,因而得名。

less 是 STL 中最常用的函数对象类模板,其定义如下:
1.template <class_Tp>
2.struct less
3.{
4. bool operator() (const_Tp & __x, const_Tp & __y) const
5. { return __x < __y; }
6.};
要判断两个 int 变量 x、y 中 x 是否比 y 小,可以写:
if( less()(x, y) ) { … }

http://c.biancheng.net/view/354.html

C++关联容器,STL关联容器
关联容器内部的元素都是排好序的,有以下四种。
set:排好序的集合,不允许有相同元素。
multiset:排好序的集合,允许有相同元素。
map:每个元素都分为关键字和值两部分,容器中的元素是按关键字排序的。不允许有多个元素的关键字相同。
multimap:和 map 类似,差别在于元素的关键字可以相同。
http://c.biancheng.net/view/357.html

STL算法分类
在 STL 中,算法就是函数模板。STL 中的算法大多数是用来对容器进行操作的,如排序、 查找等。大部分算法都是在头文件 中定义的,还有些算法用于数值处理,定义在头文件 中。

不同的教程对 STL 中的算法有不同的分类方法。本教程将算法分为以下七类:
1.不变序列算法。
2.变值算法。
3.删除算法。
4.变序算法。
5.排序算法。
6.有序区间算法。
7.数值算法。
http://c.biancheng.net/view/397.html

C++ string类(C++字符串)完全攻略

string 类是 STL 中 basic_string 模板实例化得到的模板类。其定义如下:
typedef basic_string string;

  1. 构造函数
    string 类有多个构造函数,用法示例如下:
    1.string s1(); // si = “”
    2.string s2(“Hello”); // s2 = “Hello”
    3.string s3(4, ‘K’); // s3 = “KKKK”
    4.string s4(“12345”, 1, 3); //s4 = “234”,即 “12345” 的从下标 1 开始,长度为 3 的子串
    为称呼方便,本教程后文将从字符串下标 n 开始、长度为 m 的字符串称为“子串(n, m)”。

string 类没有接收一个整型参数或一个字符型参数的构造函数。下面的两种写法是错误的:
1.string s1(‘K’);
2.string s2(123);
2. 对 string 对象赋值
可以用 char* 类型的变量、常量,以及 char 类型的变量、常量对 string 对象进行赋值。例如:
1.string s1;
2.s1 = “Hello”; // s1 = “Hello”
3.s2 = ‘K’; // s2 = "K”
string 类还有 assign 成员函数,可以用来对 string 对象赋值。assign 成员函数返回对象自身的引用。例如:
1.string s1(“12345”), s2;
2.s3.assign(s1); // s3 = s1
3.s2.assign(s1, 1, 2); // s2 = “23”,即 s1 的子串(1, 2)
4.s2.assign(4, ‘K’); // s2 = “KKKK”
5.s2.assign(“abcde”, 2, 3); // s2 = “cde”,即 “abcde” 的子串(2, 3)
3. 求字符串的长度
length 成员函数返回字符串的长度。size 成员函数可以实现同样的功能。
4. string对象中字符串的连接
除了可以使用+和+=运算符对 string 对象执行字符串的连接操作外,string 类还有 append 成员函数,可以用来向字符串后面添加内容。append 成员函数返回对象自身的引用。例如:
1.string s1(“123”), s2(“abc”);
2.s1.append(s2); // s1 = “123abc”
3.s1.append(s2, 1, 2); // s1 = “123abcbc”
4.s1.append(3, ‘K’); // s1 = “123abcbcKKK”
5.s1.append(“ABCDE”, 2, 3); // s1 = “123abcbcKKKCDE”,添加 “ABCDE” 的子串(2, 3)
5. string对象的比较
除了可以用 <、<=、==、!=、>=、> 运算符比较 string 对象外,string 类还有 compare 成员函数,可用于比较字符串。compare 成员函数有以下返回值:
小于 0 表示当前的字符串小;
等于 0 表示两个字符串相等;
大于 0 表示另一个字符串小。

例如:
1.string s1(“hello”), s2(“hello, world”);
2.int n = s1.compare(s2);
3.n = s1.compare(1, 2, s2, 0, 3); //比较s1的子串 (1,2) 和s2的子串 (0,3)
4.n = s1.compare(0, 2, s2); // 比较s1的子串 (0,2) 和 s2
5.n = s1.compare(“Hello”);
6.n = s1.compare(1, 2, “Hello”); //比较 s1 的子串(1,2)和"Hello”
7.n = s1.compare(1, 2, “Hello”, 1, 2); //比较 s1 的子串(1,2)和 “Hello” 的子串(1,2)
6. 求 string 对象的子串
substr 成员函数可以用于求子串 (n, m),原型如下:
string substr(int n = 0, int m = string::npos) const;
调用时,如果省略 m 或 m 超过了字符串的长度,则求出来的子串就是从下标 n 开始一直到字符串结束的部分。例如:
1.string s1 = “this is ok”;
2.string s2 = s1.substr(2, 4); // s2 = “is i”
3.s2 = s1.substr(2); // s2 = “is is ok”
7. 交换两个string对象的内容
swap 成员函数可以交换两个 string 对象的内容。例如:
1.string s1("West”), s2(“East”);
2.s1.swap(s2); // s1 = “East”,s2 = “West”
8. 查找子串和字符
string 类有一些查找子串和字符的成员函数,它们的返回值都是子串或字符在 string 对象字符串中的位置(即下标)。如果查不到,则返回 string::npos。string: :npos 是在 string 类中定义的一个静态常量。这些函数如下:
find:从前往后查找子串或字符出现的位置。
rfind:从后往前查找子串或字符出现的位置。
find_first_of:从前往后查找何处出现另一个字符串中包含的字符。例如:
s1.find_first_of(“abc”); //查找s1中第一次出现"abc"中任一字符的位置
find_last_of:从后往前查找何处出现另一个字符串中包含的字符。
find_first_not_of:从前往后查找何处出现另一个字符串中没有包含的字符。
find_last_not_of:从后往前查找何处出现另一个字符串中没有包含的字符。

下面是 string 类的查找成员函数的示例程序。
1.#include
2.#include
3.using namespace std;
4.int main()
5.{
6. string s1(“Source Code”);
7. int n;
8. if ((n = s1.find(‘u’)) != string::npos) //查找 u 出现的位置
9. cout << “1) " << n << “,” << s1.substr(n) << endl;
10. //输出 l)2,urce Code
11. if ((n = s1.find(“Source”, 3)) == string::npos)
12. //从下标3开始查找"Source”,找不到
13. cout << “2) " << “Not Found” << endl; //输出 2) Not Found
14. if ((n = s1.find(“Co”)) != string::npos)
15. //查找子串"Co”。能找到,返回"Co"的位置
16. cout << "3) " << n << ", " << s1.substr(n) << endl;
17. //输出 3) 7, Code
18. if ((n = s1.find_first_of(“ceo”)) != string::npos)
19. //查找第一次出现或 ‘c’、'e’或’o’的位置
20. cout << "4) " << n << ", " << s1.substr(n) << endl;
21. //输出 4) l, ource Code
22. if ((n = s1.find_last_of(‘e’)) != string::npos)
23. //查找最后一个 ‘e’ 的位置
24. cout << "5) " << n << ", " << s1.substr(n) << endl; //输出 5) 10, e
25. if ((n = s1.find_first_not_of(“eou”, 1)) != string::npos)
26. //从下标1开始查找第一次出现非 ‘e’、‘o’ 或 ‘u’ 字符的位置
27. cout << “6) " << n << “, " << s1.substr(n) << endl;
28. //输出 6) 3, rce Code
29. return 0;
30.}
9. 替换子串
replace 成员函数可以对 string 对象中的子串进行替换,返回值为对象自身的引用。例如:
1.string s1(“Real Steel”);
2.s1.replace(1, 3, “123456”, 2, 4); //用 “123456” 的子串(2,4) 替换 s1 的子串(1,3)
3.cout << s1 << endl; //输出 R3456 Steel
4.string s2(“Harry Potter”);
5.s2.replace(2, 3, 5, ‘0’); //用 5 个 ‘0’ 替换子串(2,3)
6.cout << s2 << endl; //输出 HaOOOOO Potter
7.int n = s2.find(“OOOOO”); //查找子串 “00000” 的位置,n=2
8.s2.replace(n, 5, “XXX”); //将子串(n,5)替换为"XXX”
9.cout << s2 < < endl; //输出 HaXXX Potter
10. 删除子串
erase 成员函数可以删除 string 对象中的子串,返回值为对象自身的引用。例如:
1.string s1(“Real Steel”);
2.s1.erase(1, 3); //删除子串(1, 3),此后 s1 = “R Steel”
3.s1.erase(5); //删除下标5及其后面的所有字符,此后 s1 = “R Ste”
11. 插入字符串
insert 成员函数可以在 string 对象中插入另一个字符串,返回值为对象自身的引用。例如:
1.string s1(“Limitless”), s2(“00”);
2.s1.insert(2, “123”); //在下标 2 处插入字符串"123”,s1 = “Li123mitless”
3.s1.insert(3, s2); //在下标 2 处插入 s2 , s1 = “Li10023mitless”
4.s1.insert(3, 5, ‘X’); //在下标 3 处插入 5 个 ‘X’,s1 = “Li1XXXXX0023mitless”
12. 将 string 对象作为流处理
使用流对象 istringstream 和 ostringstream,可以将 string 对象当作一个流进行输入输出。使用这两个类需要包含头文件 sstream。

示例程序如下:
1.#include
2.#include
3.#include
4.using namespace std;
5.int main()
6.{
7. string src(“Avatar 123 5.2 Titanic K”);
8. istringstream istrStream(src); //建立src到istrStream的联系
9. string s1, s2;
10. int n; double d; char c;
11. istrStream >> s1 >> n >> d >> s2 >> c; //把src的内容当做输入流进行读取
12. ostringstream ostrStream;
13. ostrStream << s1 << endl << s2 << endl << n << endl << d << endl << c <<endl;
14. cout << ostrStream.str();
15. return 0;
16.}

程序的输出结果是:
Avatar
Titanic
123
5.2
K

第 11 行,从输入流 istrStream 进行读取,过程和从 cin 读取一样,只不过输入的来源由键盘变成了 string 对象 src。因此,“Avatar” 被读取到 s1,123 被读取到 n,5.2 被读取到 d,“Titanic” 被读取到s2,‘K’ 被读取到 c。

第 12 行,将变量的值输出到流 ostrStream。输出结果不会出现在屏幕上,而是被保存在 ostrStream 对象管理的某处。用 ostringstream 类的 str 成员函数能将输出到 ostringstream 对象中的内容提取出来。
13. 用 STL 算法操作 string 对象
string 对象也可以看作一个顺序容器,它支持随机访问迭代器,也有 begin 和 end 等成员函数。STL 中的许多算法也适用于 string 对象。下面是用 STL 算法操作 string 对象的程序示例。
纯文本复制
1.#include
2.#include
3.#include
4.using namespace std;
5.int main()
6.{
7. string s(“afgcbed”);
8. string::iterator p = find(s.begin(), s.end(), ‘c’);
9. if (p!= s.end())
10. cout << p - s.begin() << endl; //输出 3
11. sort(s.begin(), s.end());
12. cout << s << endl; //输出 abcdefg
13. next_permutation(s.begin(), s.end());
14. cout << s << endl; //输出 abcdegf
15. return 0;
16.}
http://c.biancheng.net/view/400.html

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值