现在的位置: 首页 > 综合 > 正文

【第四章—第七章】【初识C++ Accelerated C++ 学习笔记】

2017年12月15日 ⁄ 综合 ⁄ 共 15952字 ⁄ 字号 评论关闭

第四章 组织程序和数据

1、异常(exception)

C++程序在运行时会遇到的不正常情况,比如:0作为除数、数组下标越界、打开不存在的文件、远程机器连接超时等等,一旦出现这些问题,会引发算法失效、程序运行时无故停止等现象,这就要求我们在设计软件算法时要考虑周全,而使用C++的异常机制是最常见的办法。

异常的抛出和处理主要使用三个关键字:throw、try、catch

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <stdexcept>

using std::cout;
using std::endl;
using std::domain_error;

double fun ( double x, double y )
{
    if ( 0 == y ) {
        throw domain_error ( "Error of dividing zero." );
    }   // 抛出异常
    return x / y;
}

int main ()
{
    double ans;

    try {
        ans = fun ( 23 );
        cout << "2/3 is: " << ans << endl;

        ans = fun ( 40 );
        cout << "4/0 is: " << ans << endl;
    } catch ( domain_error e ) {
        cout << e.what();
    }   // 捕获异常

    return 0;
}

当一个程序抛出一个异常时,程序的执行就会停止在程序中出现throw的地方,并且把异常对象传递到程序的另一个部分。异常对象中含有调用程序可以用来处理异常的信息。

传递的信息中最重要的部分常常纯粹是一个异常被抛出的事实。抛出异常的事实,连同异常对象的类型,已经足以让调用程序知道该如何处理。domain_error是一个标准库定义在头文件<stdexcept>中的异常类型,它用来说明一个函数的实参是这个函数不能接受的。当我们创建一个domain_error对象来抛出的时候,可以给它一个字符串,用来描述什么地方出错了。捕捉这个异常的程序,可以把这个字符串用在诊断信息中

1
throw domain_error ( "Error of dividing zero." );

try尝试执行其后{}中的语句,如果这些语句中有一处发生了domain_error异常,程序就会转到执行catch子句。

每个标准库异常,比如domain_error,都有一个可选参数,可以用来描述引起异常的问题。每种标准库异常都可以通过一个名叫what的成员函数,来取得这个参数的一份副本。

1
2
3
4
catch ( domain_error e )
{
    cout << e.what();
}

处理机制:异常事件在C++中表示为异常对象(exception object)。异常事件发生时,由操作系统为程序设置当前异常对象,然后执行程序的当前异常处理代码块,在包含了异常出现点的最内层的try块,依次匹配同级的catch语句。如果匹配catch语句成功,则在该catch块内处理异常;然后执行当前try...catch...块之后的代码。如果在当前的try...catch...块没有能匹配该异常对象的catch语句,则由更外一层的try...catch...块处理该异常;如果当前函数内的所有try...catch...块都不能匹配该异常,则递归回退到调用栈的上一层函数去处理该异常。如果一直回退到主函数main()都不能处理该异常,则调用系统函数terminate()终止程序。

 

标准异常类定义在C++标准程序库的四个头文件中:

<exception>中定义了exception类

<new>中定义了bad_alloc类

<type_info>中定义了bad_cast类

<stdexcept>中定义了runtime_error、logic_error类

所有的异常类都是exception类的子类。

runtime_error类(表示运行时才能检测到的异常)包含了overflow_error、underflow_error、range_error几个子类;

logic_error类(一般的逻辑异常)包含了domain_error、invalid_argument、out_of_range、length_error几个子类;

各种标准异常类都定义了一个接受字符串的构造函数,字符串初始化式用于为所发生的异常提供更多的信息。所有异常类都有一个what()虚函数,它返回一个指向C风格字符串的指针。

应用程序可以从各种标准异常类派生自已的异常类。

 

一些目前还看不明白的参考:

异常处理中需要注意的问题

  • 如果抛出的异常一直没有函数捕获(catch),则会一直上传到c++运行系统那里,导致整个程序的终止
  • 一般在异常抛出后资源可以正常被释放,但注意如果在类的构造函数中抛出异常,系统是不会调用它的析构函数的,处理方法是:如果在构造函数中要抛出异常,则在抛出前要记得删除申请的资源。
  • 异常处理仅仅通过类型而不是通过值来匹配的,所以catch块的参数可以没有参数名称,只需要参数类型。
  • 函数原型中的异常说明要与实现中的异常说明一致,否则容易引起异常冲突。
  • 应该在throw语句后写上异常对象时,throw先通过Copy构造函数构造一个新对象,再把该新对象传递给 catch。那么当异常抛出后新对象如何释放?异常处理机制保证:异常抛出的新对象并非创建在函数栈上,而是创建在专用的异常栈上,因此它才可以跨接多个函数而传递到上层,否则在栈清空的过程中就会被销毁。所有从try到throw语句之间构造起来的对象的析构函数将被自动调用。但如果一直上溯到main函数后还没有找到匹配的catch块,那么系统调用terminate()终止整个程序,这种情况下不能保证所有局部对象会被正确地销毁。
  • catch块的参数推荐采用地址传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常扑获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获。
  • 编写异常说明时,要确保派生类成员函数的异常说明和基类成员函数的异常说明一致,即派生类改写的虚函数的异常说明至少要和对应的基类虚函数的异常说明相同,甚至更加严格,更特殊。


  • 异常处理仅仅通过类型而不是通过值来匹配的,否则又回到了传统的错误处理技术上去了,所以catch块的参数可以没有参数名称,只需要参数类型,除非要使用那个参数。
  • 虽然异常对象看上去像局部对象,但并非创建在函数栈上,而是创建在专用的异常栈上,因此它才可以跨接多个函数而传递到上层,否则在栈清空的过程中就会被销毁。
  • 函数原型中的异常说明要与实现中的异常说明一致,否则容易引起异常冲突。由于异常处理机制是在运行时有异常时才发挥作用的,因此如果函数的实现中抛出了没有在其异常说明列表中列出的异常,则编译器并不能检查出来。但是当运行时如果真的抛出了这样的异常,就会导致异常冲突。因为你没有提示函数的调用者:该函数会抛出一种没有被说明的即不期望的异常,于是异常处理机制就会检测到这个冲突并调用标准库函数unexcepted(),unexcepted()的默认行为就是调用terminate()来结束程序。实际工作中使用set_unexcepter()来预设一个回调函数。
  • 当异常抛出时局部对象如何释放?Bjarne Stroustrup引入了“resource acquistion isinitialization”思想,异常处理机制保证:所有从try到throw语句之间构造起来的局部对象的析构函数将被自动调用,然后清退堆栈(就像函数正常退出一样)。如果一直上溯到main函数后还没有找到匹配的catch块,那么系统调用terminate()终止整个程序,这种情况下不能保证所有局部对象会被正确地销毁。
  • catch块的参数应采用引用传递而不是值传递,不仅可以提高效率,还可以利用对象的多态性。另外,派生类的异常扑获要放到父类异常扑获的前面,否则,派生类的异常无法被扑获。catch(void *)要放到catch(...)前面。
  • 编写异常说明时,要确保派生类成员函数的异常说明和基类成员函数的异常说明一致,即派生类改写的虚函数的异常说明至少要和对应的基类虚函数的异常说明相同,甚至更加严格,更特殊。

REFERENCE:

http://ticktick.blog.51cto.com/823160/191881

http://zh.wikipedia.org/wiki/C%2B%2B%E5%BC%82%E5%B8%B8%E5%A4%84%E7%90%86

http://blog.csdn.net/tuwen/article/details/2295853

另外,关于异常和错误比较深入的探讨可以参考刘未鹏的博客:

http://blog.csdn.net/pongba/article/details/1815742

__________________________________________________________________________________________________________________________________________________________________________________

 

2、函数的重载(overload)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using std::cout;
using std::endl;

int sum ( int x, int y )
{
    return x + y;
}

double sum ( double x, double y )
{
    return x + y;
}

int main ()
{
    cout << sum(34) << endl;
    cout << sum(3.24.3) << endl;

    return 0;
}

让几个函数具有相同的名字,这就是函数的重载。系统可以通过调用时,实参的数目和类型来区分具有相同名字的重载函数。具有相同类型参数表、仅在返回值类型上不同的重载函数会引起错误。

为什么需要函数重载?

  • 可以用相同的函数名来定义一组功能相同或类似的函数,程序的可读性增强。
  • 试想如果没有函数重载机制,如在C中,你必须要这样去做:为这个sum函数取不同的名字,如sum_int、sum_double。这里还只是两个的情况,如果是很多个的话,就需要为实现同一个功能的函数取很多个名字,这样做很不友好!
  • 类的构造函数跟类名相同,也就是说:构造函数都同名。如果没有函数重载机制,要想实例化不同的对象,那是相当的麻烦!
  • 操作符重载,本质上就是函数重载,它大大丰富了已有操作符的含义,方便使用,如+可用于连接字符串等!

 

REFERENCE:

http://www.cnblogs.com/skynet/archive/2010/09/05/1818636.html

http://blog.csdn.net/sendy888/article/details/1738997

__________________________________________________________________________________________________________________________________________________________________________________

 

3、引用

C++中的引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。

使用引用时要注意:

  • 引用必须在定义的同时初始化
  • 外部(extern)引用定义不必给出初值
  • 引用初始化后不能再使其成为其它变量的引用
  • 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等。
  • 不能建立数组的引用。

 

常引用

常引用声明方式:const 类型标识符 &引用名=目标变量名;

用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性

假设有如下函数声明:

1
2
string foo( );
void bar(string &s);

那么下面的表达式将是非法的:

1
2
bar(foo( ));
bar("hello world");

原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。

 

另外一个例子:

1
2
3
4
vector<string> str;
vector<string> &s1 = str;
const vector<string> &cs = str;
vector<string> &s2 = s1;

以上的程序片段中,s1就是str的别名(引用),对s1的任何操作都等同于直接对str的操作。cs也是str的别名,只不过,我们只能通过cs引用str,并不能修改str(常引用)。s2看似是s1的别名,但是s1本身就是str的别名,所以s2其实就是str的别名。

1
2
3
const vector<string> str1;
vector<string> &s3 = cs;
vector<string> &s4 = str1;

这段代码中的两个引用都是错误的,因为cs是一个常引用,str1是一个常量对象,我们不能使用非常量的别名来指代常量对象或者常量引用。但是可以使用常量别名引用非常量对象或引用。类似的,如果函数的形参类型为const vector<string> &,就可以使用常量或者非常量的实参,但是如果函数参数类型为vector<string> &,则只能使用非常量的实参,不能使用常量实参。

 

引用的作用:

引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。C++中又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。

使用指针作为函数的参数虽然也能达到与使用引用相同的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用

 

引用型参数应该在能被定义为const的情况下,尽量定义为const

  • 以引用返回函数值,定义函数时需要在函数名前加&
  • 用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。

 

注意:

  • 在引用的使用中,单纯给某个变量取个别名是毫无意义的,引用的目的主要用于在函数参数传递中,解决大块数据或对象的传递效率和空间不如意的问题。
  • 用引用传递函数的参数,能保证参数传递中不产生副本,提高传递的效率,且通过const的使用,保证了引用传递的安全性。
  • 引用与指针的区别是,指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。


使用引用的时机:

必须使用引用流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数

下面的情况都是推荐使用引用,但是也可以不使用引用。如果不想使用引用,完全可以使用指针或者其它类似的东西替代:

  • 异常catch的参数表
  • 大对象作为参数传递
  • 返回容器类中的单个元素
  • 返回类数据成员(非内建数据类型成员)
  • 返回其它持久存在的,且获得者不负责销毁的对象

另外一些情况下,不能返回引用:+-*/四则运算符


REFERENCE:

http://jpkc.jwc.bupt.cn:4213/C++/down/c++paper/C++%E4%B8%AD%E7%9A%84%E5%BC%95%E7%94%A8.htm

http://developer.51cto.com/art/201107/277349.htm

__________________________________________________________________________________________________________________________________________________________________________________

 

4、C++中头文件(.h)和源文件(.cpp)各应该(可以)包含哪些内容?

头文件中:

  • 文件中所使用的变量或者类型所在的头文件
  • #define常数
  • 函数原型(使用完整的生存空间不能使用using,如果在头文件中使用了using声明,会影响包含这个头文件的.cpp文件对生存空间的选择,头文件不应该影响.cpp文件对变量和库的选择!)
  • 结构体的定义
  • 为了防止重复包含的预处理指令,比如:

1
2
3
4
5
6
7
#ifndef GUARD_median_h
#define GUARD_median_h

#include <vector>
double median ( std::vector<double> );

#endif


源文件中:

  • 文件中所使用的变量或者类型所在的头文件(可以与.h文件中所包含的头文件重复)
  • using语句
  • 函数的定义

 

头文件包含的原则:

只要文件(.h和.cpp)中出现的变量或者类型不是定义于或声明于本文件中的,我们就应该包含那些声明或者定义了这些变量或者类型的头文件。


REFERENCE:

http://blog.csdn.net/lyanliu/article/details/2195632

===========================================================================================================

 

 

第五章 使用序列式容器并分析字符串

1、索引 vs 迭代器

顺序访问:存取第N个数据时,必须先访问前(N-1)个数据(比如:list)

随机访问:存取第N个数据时,不需要访问前(N-1)个数据,直接就可以对第N个数据操作(比如:vector)

索引只支持能够随机访问的容器的元素,而迭代器可以访问所有容器的元素。

C++标准库提供了叫做迭代器(iterator)的类型系列,使用它可以按照标准库控制的方式来访问数据结构。这种控制使得标准库可以高效地实现。

一个迭代器是一个值,它能够

  • 标识一个容器和容器中的一个元素
  • 允许检测元素中保存的值
  • 提供在容器之间移动的操作
  • 使容器可用有效处理的方式来约束可用的操作

每个标准库容器,都定义了两个相关的迭代器类型:

1
2
container-type::const_iterator
container-type::iterator

系统可以把iterator类型转化为const_iterator类型,但是反向的转换是不允许的。end和begin成员函数返回值的类型为iterator。

我们常常可以把使用索引的程序改写为使用迭代器的程序。

REFERENCE:

http://bbs.csdn.net/topics/340028558

__________________________________________________________________________________________________________________________________________________________________________________

 

2、vector vs list

list类型支持在容器中的任意位置进行快速的插入和删除,但是按顺序访问较vector慢

list和vector的选择:

  • vector:按顺序访问、在容器末尾删除和插入
  • list:在容器任意位置(中间)删除和插入元素,list不支持索引操作

对迭代器的影响:

从一个vector中删除一个元素时,指向被删除元素和它之后的所有元素的迭代器都会失效(因为删除后会移动后面的所有元素)。使用push_back为vector添加一个元素,会使所有指向这个vector的迭代器失效(为新元素分配的空间可能会导致整个vector空间的重新分配)。在循环中使用这些操作时要特别小心,必须确保循环没有任何无效的迭代器的副本

对于list来说,erase和push_back操作都不会使指向其它元素的迭代器失效,只有被删除的元素的迭代器会失效

 

通用的sort函数不能使用在list容器上,因为list不支持随机访问,list自己提供了sort函数作为它的一个成员函数,专门对list中的数据排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
C++中vector和list的区别

vector 和built-in数组类似,它拥有一段连续的内存空间,并且起始地址不变,因此它能非常好的支持随即存取,即[]操作符,但由于它的内存空间是连续的,
所以在中间进行插入和删除会造成内存块的拷贝,另外,当该数组后的内存空间不够时,需要重新申请一块足够大的内存并进行内存的拷贝。这些都大大影响了
vector的效率。

list就是数据结构中的双向链表,因此它的内存空间可以是不连续的,通过指针来进行数据的访问,这个特点使得它的随即存取变的非常没有效率,因此它没有
提供[]操作符的重载。但由于链表的特点,它可以以很好的效率支持任意地方的删除和插入。

如果需要高效的随即存取,而不在乎插入和删除的效率,使用vector
如果需要大量的插入和删除,而不关心随即存取,则应使用list

REFERENCE:http://www.cppblog.com/sleepwom/archive/2012/02/17/165844.html

__________________________________________________________________________________________________________________________________________________________________________________

 

3、一些函数

容器的成员函数:

1
2
3
4
5
erase       c.erase(it) c.erase(b, e)删除vector中的一个元素,参数为iterator或者为iterator区间[b, e),不能操作索引。函数返回一个迭代器,指向被删除元素的下
            一个位置。
insert      c.insert(d, b, e)复制迭代器区间[b, e)所指元素,并把这些副本插入到容器c中的d位置之后。
empty       c.empty() 谓词,表示容器c中是否没有任何元素。
push_back   c.push_back(t) 把一个值为t的元素添加到容器末尾。

vector特有成员函数:

1
2
v.reserve(n)    为那个元素预留空间,但并不初始化它们。这个操作并不改变容器的长度,它影响的只是调用insert或者push_back函数时,vector内存分配的频率。
v.resize(n)     给v一个等于n的新长度。如果n小于当前v的长度,n之后的元素被删除;如果n大于当前长度,新的空间中会填充根据v包含类型来适当初始化的值。


list特有成员函数:

1
l.sort()  l.sort(cmp)   使用list包含的类型操作符 < 或者谓词cmp来对list中的成员排序。

string的一些函数:

1
2
s.substr(i, j)  使用s中索引标识为[i, i + j)之间的元素创建一个新的字符串。
getline(is, s)  从流is中读取一行输入,并保存于s中。

===========================================================================================================

 

 

第六章 使用库算法

1、迭代器适配器

它是可以产生迭代器的函数,并且产生的迭代器与它带有的参数(参数为一个容器,返回一个迭代器)有相关的属性。迭代器适配器定义在头文件<iterator>中,

STL提供了许多基于迭代器的适配器,如back_insert_iterator,front_insert_iterator,insert_iterator, reverse_iterator,istream_iterator,ostream_iterator,istreambuf_iterator,ostreambuf_iterator等。

这些适配器大致可以分为三类:插入迭代器、反向迭代器和IO迭代器


REFERENCE:

http://www.cnblogs.com/cobbliu/archive/2012/04/17/2440347.html

__________________________________________________________________________________________________________________________________________________________________________________

 

2、重载函数的使用

不能直接使用重载函数的函数名直接作为其他函数的参数,因为系统无法区分使用的是哪个版本的重载函数,我们应该额外使用一个辅助函数来代替原重载函数作为参数进行传递。

1
2
3
4
5
6
7
8
9
10
11
int sum (  int x, int y );
double sum ( double x, double y );

compute ( a, b, sum );  // 使用错误

sum_aux ( double x, double y )  // 定义辅助函数
{
    return sum ( x, y );
}

compute ( a, b, sum_aux );  // 正确使用

__________________________________________________________________________________________________________________________________________________________________________________

 

3、库算法与成员函数

库算法(sort/remove_if/partition等)是作用在容器元素上的,并不是作用在容器本身上,不改变容器本身的属性,而容器的成员函数(erase/push_back等),是直接作用在容器上的,会改变容器的属性

__________________________________________________________________________________________________________________________________________________________________________________

 

4、常见库算法

<numeric>头文件中:

1
accumulate(b, e, t)创建一个局部变量,并用t初始化,这个变量的类型与t的类型一致,并且把区间[b, e)的所有元素都加到t上,然后把得到的结果的副本返回。

<algorithm>头文件中:

1
2
3
4
5
6
find(b, e, t)
find_if(b, e, p)
search(b, e, b2, e2)
在区间[b, e)中查找给定值的算法。find查找值t,并返回第一个t的位置,如果没有值为t的元素,则返回e;find_if用谓词p检测每个元素,并返回第一个
使谓词为真的位置,如果没有使谓词为真的元素,则返回e;search查找区间[b2, e2)所表示的值序列,并返回第一个[b2, e2)所表示的值序列的位置,如
果没有此序列,则返回e。


1
2
3
4
5
copy(b, e, d)
remove_copy(b, e, d, t)
remove_copy_if(b, e, d, p)
把区间[b, e)中的序列复制到d指代的目的地的算法。copy复制整个序列;remove_copy复制不等于t的所有元素;remove_copy_if复制所有使得谓词p
为假的元素。

1
2
3
4
remove_if(b, e, p)    排列容器以使在区间[b, e)中使谓词p为假的元素位于这个区间的头部。返回一个迭代器,这个迭代器指示了位于那些
                      不被“删除”的元素之后的那个位置。
remove(b, e, t)       作用与remove_if一样,但是只是检测那些元素不等于t的值。
transform(b, e, d, f) 对区间[b, e)中的元素执行函数f,并把结果存储在d中。


1
2
3
4
5
partition(b, e, p)
stable_partition(b, e, p)
以谓词p划分区间[b, e)中的元素,让那些使谓词为真的元素处于容器的头部。返回一个迭代器,指向第一个使谓词为假的元素;或者,
如果对所有的元素谓词都为真,那就返回e。
stable_partition保证在划分的每一个区间中使元素原来的顺序保持不变。

__________________________________________________________________________________________________________________________________________________________________________________

 

5、回文实例

使用库算法来判断回文

1
2
3
4
bool is_palindrome ( const string &s )
{
    return equal ( s.begin(), s.end(), s.rbegin() );
}

equal函数比较两个序列,判断他们是否含有相同的值。在上面的函数中,第一个序列区间是(s.begin(),s.end()],第二个区间看似表达有误,只有一个端点s.rbegin(),但是equal函数假定第二个序列和第一个序列的长度相同,所以,第二个区间只需要起点就够了,不需要终点,其实这个终点就是s.rend()。由于(s.begin(),
s.end()]是表示s字符串中第一个字符到最后一个字符的序列,而(s.rbegin(), s.rend()]表示的是s中最后一个字符带第一个字符的序列,显然,通过比较这两个序列,就可以判断s是否为回文。

===========================================================================================================

 

 

第七章 使用关联式容器

1、关联式容器——map

vector和list都是序列式容器(Sequential containers),序列式容器中的元素都是按照指定的次序来排列的,当使用push_back或者insert后,之前的元素顺序不发生改变。

 

map是关联式容器(Associative containers),其中的元素是按照本身的值进行排列的,而不是按照我们插入的先后顺序排列的,我们不需要对关联式容器进行排序,关联式容器会根据自身的排序方法,让我们更快地查找特定的值。

map中的每个元素都是一个pair,每个pair都有一个用来进行查找的键(key)和这个键所关联的值(value),我们把这种pair称为键值对(key-value
pair)
,每个值都和一个独一无二的键相对应,这样我们就可以根据元素的键,来快速地插入或者读取对应的元素。当我们把一个特定的pair插入到map时,这个键一直会伴随同一个值,直到我们删除这个pair。

key作为map的索引,既可以是整数,也可以是字符串或者是其他可以通过比较进行排序的类型

map<K, V>这个map,键的类型为K,值的类型为V,map中的pair为pair<const K, V>,pair中的键都是const的,所以那些试图改变键的操作都是被禁止的。

如果ite是map的迭代器,通过ite->first访问键,ite->second访问值。

 

map<K, V> m(cmp) 创建一个新的空map,其中的键类型为const K,值类型为V,并使用谓词cmp来决定元素的顺序。

m[k]使用类型为K的键k来索引这个map,并返回类型为V的左值。如果map中并没有这个k键,系统就会用这个键创建一个新的值初始化的元素,并把它插入到这个map中。由于在map上使用[]可能会创造一个新的元素,所以[]不能使用在const
map对象上。

m.begin() m.end() 生成map的迭代器,对这个迭代器解引用得到的是一个pair,并不是一个值。

 

关联式容器可用提供高效的方法来查找包含特定值的元素,而且还可以提供附加的信息。

由于关联式容器是自排序的,所以我们的程序不可能改变元素的顺序,因此,改变容器内容的算法往往并不适用于关联式容器。

__________________________________________________________________________________________________________________________________________________________________________________

 

2、关于递归的解释

很多方法看起来是不可能有效的,递归就是这样的方法之一。

理解递归的关键就是开始去理解它,剩下的自然就水到渠成了。

__________________________________________________________________________________________________________________________________________________________________________________

 

3、具有默认参数的函数

在C++中参数可以设置缺省值,设置了缺省值之后,这个参数在调用时就可以省略。

注意:设置缺省值的参数只能是最后的几个参数。也就是说某一个参数一旦设置了缺省值,其后而的参数也必须设置缺省值。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

int sum(int x = 0int y = 100int z = 0)
{
    return x + y + z;
}
//int sum(int x, int y=100, int z=0) { ... }   //这是正确的
//int sum(int x, int y, int z=0) { ... }       //这也是正确的
//int plus(int x, int y=100, int z) { ... }    //这是错误的

int main ( )
{
    cout << sum() << endl;
    cout << sum(6) << endl;
    cout << sum(610) << endl;
    cout << sum(61020) << endl;

    return 0;
}

c++函数的默认参数在哪里定义

答:函数原型声明里和函数定义中都行。但是必须满足两个规则:

  • 只能在函数声明和函数定义中选一个,不能两个都定义默认参数。
  • 默认参数无论定义在函数声明还是函数定义中,必须位于调用该函数的函数前面。

例子:

可以通过编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

int add(int m1, int m2, int m3, int m4);

int add(int m1, int m2, int m3 = 0int m4 = 0)
{
    return m1 + m2 + m3 + m4;
}

void main()
{
    cout << add(13) << "," << add(135) << "," << add(1357) << endl;
}

不能通过编译的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

int add(int m1, int m2, int m3, int m4);

void main()
{
    cout << add(13) << "," << add(135) << "," << add(1357) << endl;
}

int add(int m1, int m2, int m3 = 0int m4 = 0)
{
    return m1 + m2 + m3 + m4;
}

REFERENCE:

http://www.quanxue.cn/JC_CLanguage/Cpp/Cpp03.html

http://finaleden.iteye.com/blog/580428

http://blog.csdn.net/weiwangchao_/article/details/6928645

===========================================================================================================

 

抱歉!评论已关闭.