1、递归实现的原理
每个函数都可以直接或间接的调用自己叫做递归;所谓间接调用是指在递归函数调用的下层函数中再调用自己。
函数之所以可以实现递归,是因为函数的每个执行过程在栈中都有自己的形参和局部变量的副本,这些副本和该函数的其他执行过程毫不相关。这种机制是当代大多数程序设计语言实现子程序结构的基础,也使得递归成为可能。每执行一次函数调用,都会在栈里面保存一份此刻该函数的一组参数和自变量,直到该函数执行到一个出口点。程序遍历执行这些函数的过程叫递归下沉。
应该还想到递推和递归的区别,递推是从已知推到要求的未知;而递归是从要求的未知推到已知的出口点。所谓出口点也就是必须有一个终止条件来结束递归下降过程,并回溯到顶层。
注意:函数是在从递归下降过程回溯时才真正开始工作的。
知道了以上这些才明白当时老师将递归的时候为什么说:递归就像几个人在踢皮球一样,彼此推卸责任。不到万不得已不要用递归实现。时间和空间效率都太低。呵呵,但是如果你看过数据结构的书你就会知道树、图这些复杂的数据类型都是用递归来定义的。所以我感觉,递归在程序设计里面的分量绝不是在我们程序设计基础里面的地位,是一个很值得研究的问题。
2、跳转语句goto
不能用goto语句跳过包含隐式或显示初始化变量的声明语句。如:
……
goto next;
int b=8;(显示初始化变量)
int a=b+3;(隐式初始化变量)
next:
……
3、如果将内存访问次数减半的同时会导致比较次数的加倍,我们依然能够降低总的查找时间。
访问一次内存的时间~~100算术逻辑运算
访问一次外存的时间~~10000次算术运算
4、wchar_t型变量
wchar_t也是字符类型,但是是16位,可以支持UTF-8字符集。有专门的输入输出对象:
wchar_t wc;
wout<
win>>wc;
5、安全连接
如果在不同的转换单元(源代码文件,编译是以一个文件为单位的)中声明了多个同名函数,那么如何解决相同函数名带来的冲突那?连接程序似乎很难分辨出调用的到底是哪个函数。这时,函数连接就是不安全的。
为了解决这个问题,C++采用了一种称为名字重组(name mangling)的技术,给编译器内部的函数标识符重新命名。重组后的函数名包含了指定函数返回值类型和参数类型的符号。在可重定位的源文件里,对函数的调用以及函数的定义都被转换成重组后的名字,而这个名字是唯一的。
命名重组要比圆形说明更有效,因为C++和C不同,简单地用不同的原型来说明同一个函数是无法通过C++的类型检查的。命名重组在不同的编译器里是不一样。
尽管C++可以重组函数名,但是其他语言(特别是C编译器和汇编语言的汇编程序)是没有这个功能的。这就会导致C++在调用C和汇编的程序时,会自动进行函数名重组,这样会导致,被调用的函数就会找不到正确的调用函数。所以,有些时候必须通知C++编译器。
下面这段代码说明了当某个头文件包含了一个用C编译器编译过的函数时,如何通过链接说明来通知C++编译器:
extern “C” //the linkage specifition
{
#include “mychdr.h” //tell C++ that function in the library were complied with c
}
int main()
{
return foobar();
}
6、全局作用域解析符
但一个变量和一个全局变量具有相同的名字时,当程序处于局部变量的作用域时,对所有该变量的引用都会指向局部变量。这是如果一定使用全局变量,可以利用”::”来使用该变量。
cout<<::amount
cout<
7、文件作用域
文件作用域是指外部标识符尽在声明他的同一个转换单元内的函数可见。所谓,转换单元是指定义这些变量和函数的源代码文件。
static a;则改变量的作用域是该转换单元。
8、存储类
存储类有以下四种:
auto(自动):每次执行到定义该变量的语句块时,都将在内存中为改变量产生一个新的副本,并对其进行初始化。为变量的默认值。
static(静态):在该文件中初始化值执行一次。
extern(外部):声明了程序将要用到的但尚未定义的外部变量。通常,外不存粗类都用于声明在另一个转换单元中定义的变量。
register(寄存器):使用寄存器存储类的目的是让程序员指定把某个局部变量存放在计算机的某个硬件寄存器而不是主存中,以提高程序的运行效率。不过这只反映了程序员的主观意愿,编译器可以忽略register存储类修饰符。用户无法获得寄存器变量的地址,以为绝大多数计算机的硬件寄存器都不占用内存地址。必须向汇编语言程序员那样了解处理器结构的内部架构,知道可用于存放变量的寄存器的数量。种类以及工作方式。
9、类型修饰符
const:用于不需要修改变量内容的场合。
volatile:他通知编译器,程序将以某些不可见得方式改变变量的值。设想一下,程序吧一个变量的地址传递给一个外部指针,程序和系统的某个终端服务例程通过指针修改变量的内容,如果编译器优化函数时把变量存放到寄存器里,那么终端就不起作用了。所以volatile就是告诉编译器不要对此变量进行这样的优化。
10、用户自定义数据类型
union:每一次对union的一个成员赋值都会改变其他成员的值,因为所有的成员公用一块内存。只能对联合的第一个变量进行初始化。
匿名联合,C++中允许声明无名的联合。可以节省空间或有意地重新定义变量。
enum:枚举常量
例如: enum Colors{red,blue,green};其中Colors[0]/Colors[1]/Colors[2]表示。
在声明枚举常量时,可以对某个特定的标识符指定其对应的整型值,紧随其后的标识符对应的整型值依次加1。
如:enum Colors{red=1,blue,green};其中Colors[0]=red/Colors[0]=1均成立;
11、指向指针的指针
如:
char **cpp;即:
char c=’A’; char *cp=&c; char ** cpp=&cp.
通过指针的指针,不仅可以访问它指向的指针,还可以访问它指向的指针所指向的数据。
12、void型指针
void *vptr;可以吧任何地址付给void指针,但是如果没有强制类型转换说明,就无法用void型指针得到变量的值。
13、程序的内存结构
程序存储是当代所有数字计算机的基础。程序的机器语言指令和数据都存储在同一个逻辑内存空间里。而且,程序被组织成4个逻辑段:
可执行代码段、静态数据、动态数据(堆)、栈
可执行代码段和静态数据存储在固定的内存位置。
程序请求动态分配的内存来自于称为堆的内存池。
局部数据对象、函数的参数以及调用函数和被调用函数的联系放在称为栈的内存池中。
根据操作平台和编译器的不同,堆和栈既可以是被所有同时运行的程序共享的操作系统资源,也可以是使用它们的程序独占的局部资源。
对于C++的new来说,当堆用尽的时候,那么将会抛出一个运行时异常。
14、库函数
:用于调试,该文件中定义了assert宏,使用assert宏就是断言某个条件为真。在开头定义#define NDEBUG就会让所有断言失效,不用特意删除断言。
:里面声明的函数用于转换字符变量并测试其是否在给定的范围内。
:中定义了一个值可变的全局变量errno和两个全局符号EDOM(数学函数的参数错误)和ERANGE(浮点数太大).变量errno使用宏来实现的,因此不再std里。
:声明了标准的数学函数
:类似于C++里面的中断异常处理
:在使用参数个数可变的函数时,要包含 头文件,并且要使用一条变量声明语句(va_list)和该头文件中定义的三个宏(va_start,va_arg,va_end)。
:c语言里面的输入输出库
:可分为:数字函数、内存管理函数、系统函数和随机数发生器函数16条进行详细介绍。
:字符串操作函数strcmp()/memset()
:声明了一些与时间操作有关的函数。
根据C++标准,当程序中包含上述头文件时,在这些头文件中定义的所有非宏的外部标识符都将放入上他的名字空间。
15、宏调用隐含的副作用
如:a=toupper(c++);
由于toupper是一个宏定义,那么在宏展开的时候可能会导致表达式多次求值,从而产生错误。
16、cstdlib.h头文件主要函数
数字函数:
int abs(int i);
int atoi(const char *) //字符串代表的整型,推荐使用snprintf(),stringstream()
long atol(const char *) //字符串代表的常整型
float atof(const char *) //字符串代表的浮点型值
内存管理函数
void *calloc(int sz,int n)
void *malloc(int sz)
void free()
系统函数
void abort() //程序异常终止。
void exit() //函数正常终止,它会关闭所有打开的流文件,并将传递给它的参数返回给操作系统。
int system(const char *cmd);
随机数发生器:
int rand();
void srand(unsigned int seed)
17、宏与函数,内联函数
所有带参的宏都可以用函数来实现,用宏的主要原因是,宏被展开成直接的代码,调用宏不会像调用函数那样需要额外的开销。
内联函数基本和带宏的定义一样,,只不过内联函数会进行类型检查,不会出现带参宏的副作用。
18、#字符串化运算符
出现在宏定义中的#运算符把跟在其后的参数转换成一个字符串。例:
#define Error(n) cout<<”Error” #n
相当于 cout<<”error” ”n”即cout<<”errorn”; //注意error和n之间没有空格
19、##符号链接运算符
##用于在宏定义中把参数连接到一起。预处理程序把出现在##两侧的参数合并成一个符号。
#include
using namespace std;
#define BookNum(b,c,v) b##c##v
int main()
{
unsigned bcv=BookNum(5,12,34);
return 0;
}
bvc=51234是一个长整型变量
如果定义了_UNICODE标识符,那么一个称作__T的宏就定义如下:
#define __T(x) L##x
这是相当晦涩的语法,但合乎ANSI C标准的前置处理器规范。那一对井字号称为「粘贴符号(token paste)」,它将字母L添加到宏参数上。因此,如果宏参数是"Hello!",则L##x就是L"Hello!"。
如果没有定义_UNICODE标识符,则__T宏只简单地定义如下:
#define __T(x) x
20、类的转换函数
转换构造函数:仅有一个参数,把别的数据类型的对象转换为该类的一个对象。
成员转换函数:把该类的对象转换成其他数据类型。 operator long () //long就是要转换成的类型
21、++运算符重载
Date operator ++()
{*this=*this+1;return *this;}
Date operator ++(int)
{Date tem=*this;*this=*this+1;return tem;}
22、windows系统中有两个优秀的应用程序框架:Microsoft基本类库(MFC)和Borland公司的对象窗口库(OWL).OWL广泛使用了多重继承,而MFC一点也不涉及多重继承。这两种类层次结构的设计反应了解决统一问题的对立设计观点,但他们都是运做的很好的优秀产品。
23、重载[]下标运算符,并检查是否下标越界
#include
……
template
class Array
{
T elem[b];
public:
Array(){………..}
T& operator [] (int sub)
{
assert(sub>=0&&sub
return elem[sub];
}
24、类模板的多个参数。
类模板可以包含多个参数,这些参数可以由class标识构成,也可以包含具体的数据类型。
如 template 。类模板定义中至少应该有一个参数是类。
在定义一个类的时候,如果要用到对该类的输入输出,别忘了要进行"<<”和”>>”进行运算符重载。
类模板也可以有默认的参数,如 template
下面是一个对以上知识点的概括:
#include
using namespace std;
class Date
{
protected:
int date;
int month;
public:
Date(){date=1;month=1;}
Date(int d,int m):date(d),month(m){}
friend ostream& operator << (ostream&,Date&);
};
ostream& operator <<(ostream& output,Date& tt)
{
output<<"Date:"<
<<' '<
<
return output;
}
//end of class Date
template
class Set
{
protected:
T t;
public:
Set(T bb):t(bb){}
void dispaly()
{cout<
<
};
//end of class Set
int main()
{
Set
intset(123);
intset.dispaly();
Date tt(23,12);
Set
dateset(tt);
dateset.dispaly();
return 0;
}
25、字符串操作函数
string类包含了重载的find()和rfind()函数,find()从对象的起始位置向前查找,rfind()从对象的尾部反向查找。可以用这两个函数来查找匹配的子串、单个字符或以空字符结尾的字符数组,它们返回从0开始的序号,没找到返回-1。
clear() 把字符串对象清空成0长度的值
empty()返回说明字符串是否为空的bool量
length()返回字符串的字符个数
c_str()返回指向字符串数据缓冲区的const指针
26、cin成员函数
cin.get() 可接受空字符
cin.getline()接受一行字符
cin.getline(ss,25)
cin.getline(ss,25,’q’)
27、确定文件操作位置
seekg()函数修改为输入而打开的文件中的下一个输入操作的位置.
seekp()函数修改为输出而打开的文件中的下一个输出操作的位置。
tellp()用来确定当前输出操作的位置,
tellg()用来确定当前输入操作的位置。
28、STL
标准模板库是一个容器类模板和算术函数模板的库。 算法不是方法,而是函数模板。
用户可以根据STL规则自己定义容器,所有现有的算法自动适用于新的容器。
STL容器:序列式容器(sequence container :向量vector、链表list、双端队列deque):它以线性组织的方式存储数目有限的相同类型的对象。
关联式容器(associative container):能够快速的用键值访问容器中的对象,这种容器是用键值对象和用于比较对象的比较函数构造的。采用树形结构来组织对象,支持快速、随机的检索和更新。常见的关联式容器:
集合set容器:拥有的对像都有键值,不允许键值重复。
多重集合multiset容器;对象都有键值,允许重复。
映射map容器:对象都有键值,并且每一个键值对象都与另一个参数化类型对象关联,不允许有重复的键值。
多重映射multimap容器:类似于map,但是允许有重复的键值。
容器适配器:堆栈stack、队列queue、优先级队列priority queue
分配器:每一个STL容器类都定义了一个管理容器内存分配的分配器。STL为每一个容器提供一个默认的分配器对象,用户无需直接处理内存分配。
29、序列式容器概述
vector :一种随机访问的数组类型,提供了对数组元素进行快速随机访问以及在序列尾部进行快速、随机的插入和删除操作的功能。vector对象在需要时可以修改自身的大小:vector.reserve()
deque:一种随机访问的数组类型,提供了在序列两端快速插入和删除操作的功能。deque对象在需要时可以修改自身的大小。
list :一种不支持随机访问的数组类型。STL以双向链表的方式实现,插入删除的时间是固定的。
queue:适配器容器类型,他用deque或list对象创建一个先进先出的容器。
priority_queue: 适配器容器类型,他用deque或vector对象创建一个排序队列,按照某种优先级进行排序。
stack:适配器容器类型,他用deque、list或vector对象创建一个先进后出的容器。
30、关联式容器简介
set:一种随机存取的容器,其关键字和数据元素是同一个值。不包含重复元素。
multiset:一种随机存取的容器,其关键字和数据元素是同一个值。可以包含重复元素。
map:一种包含成对数值的容器,其中,一个值是实际数据,另一个是用来寻找数据的关键字。一个特定的关键字只能与一个元素关联。
multimap:一种包含成对数值的容器,与map不同的是,它的一个关键字可与多个数据元素关联。
bitset:一种包含一系列位值(0/1)的容器,该对象中的每个元素只能是0或1
31、STL谓词
谓词是STL用来确定容器中元素信息的一种函数。调用谓词的结果(真或假)决定了容器中元素的排列方式。标准C++为用户定义了很多谓词,例如less<>,greater<>等。用户也可以自己定义一个谓词。
如:
class compare
{
public:
bool operator ()(const int c1,const int c2) const
{
std::cout<<"in compare:"<
<<"-----"<
<
return c1
}
};
调用的时候可以这样:
map charmap
注意:谓词实际上是以一种小型类的形式定义的。重载了()运算符,谓词的工作是有operator()函数来完成的。
每当程序为了维持map对象内容的顺序而需要执行比较操作的时候,就要调用这个函数。改变谓词也就是改变了STL对该映射的内容进行排序的方式。
32、通用算法概述 (一下函数都包含在std名字空间中)
非修正序列算法 :不对所作用的容器进行修改。这种算法包括:adjacent_find()、find()、find_end()、find_first()、count()、mismatch()、equal()、for_each()、search().
修正序列算法 :对所作用的容器进行修改。包括:copy()、copy_backward()、fill()、generate()、partition()、random_shuffle()、remove()、repalce()、rotate()、reserve()、swap()、swap_ranges()、transform()、unique().
排序算法 :对容器的内容进行不同方式的排序。包括:sort()、stable_sort()、partial_sort()、partial_sort_copy()以及一些相关函数,其中包括nth_element()、bianry_search()、lower_bound()、upper_bound()、equal_range()、merge()、includes()、push_heap()、pop_heap()、make_heap()、sort_heap()、set_union()、set_intersection()、set_difference()、set_symmetric_difference()、min()、min_element()、max()、max_element()、lexicographical_compare()、next_permutation()、prev_permutation()。
数值算法:对容器内容进行数值计算。包括:accumulate()、inner_product()、partial_sum()、adjacent_difference()。
可以从网上找到相关的详细函数信息。
33、迭代器。迭代器使程序能够反复的对STL容器的内容进行访问,并由此而得名。
分为5种类型:
输入迭代器 :只能够从一个容器中读取数值,可以对迭代器进行增值、引用和比较。
输出迭代器: 只能够向一个序列写入数据,可以对这种迭代器进行增值和引用。
前向迭代器 :即可读又可写,并且能够保存迭代器的值,以便从其原来的位置重新开始遍历。只能增值。
双向迭代器 :即可读又可写,可增值可减值。
随机访问迭代器 :功能最强大的迭代器类型。即可读又可写,可增值可减值,并且可进行指针运算和指针比较。
不同的STL算法需要不同类型的迭代器来实现相应的功能。因为,不同类型的STL容器支持不同类型的迭代器,所以不能对所有容器使用所有算法。
特殊用途的迭代器:流迭代器。istream_iterator和ostream_iterator。来操作与I/O流相关的数据。
为创建一个输入流迭代器,可以这样写;
istream_iterator (strm)
创建了一个输入流迭代器,字符型数据与流对象strm相关。
#include
#include
#include
using namespace std;
//the function to be called by for_each()
void show_val(int val)
{
cout<
<<'/n';
}
//the main function
int main()
{
vector
intvector;
cout<<"enter five integers separated by spaces:/n";
for(int x=0;x<5;++x)
intvector.push_back(*istream_iterator
(cin)); // 在vs2010中没有对istream_iterator定义,代码的环境为Quincy
cout<<"content is :/n";
for_each(intvector.begin(),intvector.end(),show_val);
return 0;
}
迭代器适配器:迭代器适配器将一种类型的迭代器转变为一个具有特殊用途的迭代器。
反向迭代器:对一个容器的内容进行相反方向的遍历。用户可以调用容器的rbegin()或者rend()成员函数来得到一个反向迭代器。
插入迭代器:在一个序列中插入新的数值,它所使用的语法在通常情况下会把指定位置上的数值覆盖。
34、异常处理
未经捕获的异常:是指没有为其指定catch模块的异常,或者是由作为另一次异常抛出结果而执行的析构函数抛出的异常。这种异常将导致std::terminate()函数调用,std::abort()函数来终止程序。
35、新风格类型转换
C++中有4种新风格类型转换操作符。每一种操作符都返回一个根据操作符规则转换的对象。它们的语法如下:
cast_operator (object)
cast_operator(类型转换操作符)是下面4种之一: dynamic_cast(动态类型转换)、static_cast(静态类型转换)、reinterpret_cast(重新解释类型转换)、const_cast(常类型转换)。type参数是对象要转换为的类型,object参数是要转换的对象。
其中reinterpret_cast操作替代大多数C风格类型。
35、运行时类型信息
typeid操作符支持新的C++运行时类型信息(RTTI)特性。在表达式或类型作为参数的情况下,该操作符返回一个对系统支持的type_info类型对象的引用,该对象确认参数的类型。