这个并且&&怎么不对 (c++)本人初学吉他谱 哪位大神 麻烦看看

1、return语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。例如
&&& char * Func(void)
&&&&&&& char str[] =“hello world”;// str的内存位于栈上
&&&&&&& …
&&&&&&&&&&&&& &&&&&&&&&&&&& //将导致错误
2、引用与指针的比较
引用是C++中的概念,初学者容易把引用和指针混淆一起。一下程序中,n是m的一个引用(reference),m是被引用物(referent)。
&&& int &n =
n相当于m的别名(绰号),对n的任何操作就是对m的操作。例如有人名叫王小毛,他的绰号是“三毛”。说“三毛”怎么怎么的,其实就是对王小毛说三道四。所以n既不是m的拷贝,也不是指向m的指针,其实n就是m它自己。
引用的一些规则如下:
(1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
(2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。
(3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。
&&& 以下示例程序中,k被初始化为i的引用。语句k = j并不能将k修改成为j的引用,只是把k的值改变成为6。由于k是i的引用,所以i的值也变成了6。
&&& int i = 5;
&&& int j = 6;
&&& int &k =
&&& k =& // k和i的值都变成了6;
&&& 上面的程序看起来象在玩文字游戏,没有体现出引用的价值。引用的主要功能是传递函数的参数和返回值。C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。
&&& 以下是“值传递”的示例程序。由于Func1函数体内的x是外部变量n的一份拷贝,改变x的值不会影响n, 所以n的值仍然是0。
&&& void Func1(int x)
&&& x = x + 10;
int n = 0;
&&& Func1(n);
&&& cout && “n = ” && n &&& // n = 0
以下是“指针传递”的示例程序。由于Func2函数体内的x是指向外部变量n的指针,改变该指针的内容将导致n的值改变,所以n的值成为10。
&&& void Func2(int *x)
&&& (* x) = (* x) + 10;
int n = 0;
&&& Func2(&n);
&&& cout && “n = ” && n &&& &&& // n = 10
&&& 以下是“引用传递”的示例程序。由于Func3函数体内的x是外部变量n的引用,x和n是同一个东西,改变x等于改变n,所以n的值成为10。
&&& void Func3(int &x)
&&& x = x + 10;
int n = 0;
&&& Func3(n);
&&& cout && “n = ” && n &&& &&& // n = 10
&&& 对比上述三个示例程序,会发现“引用传递”的性质象“指针传递”,而书写方式象“值传递”。实际上“引用”可以做的任何事情“指针”也都能够做,为什么还要“引用”这东西?
答案是“用适当的工具做恰如其分的工作”。
&&& 指针能够毫无约束地操作内存中的如何东西,尽管指针功能强大,但是非常危险。就象一把刀,它可以用来砍树、裁纸、修指甲、理发等等,谁敢这样用?
如果的确只需要借用一下某个对象的“别名”,那么就用“引用”,而不要用“指针”,以免发生意外。比如说,某人需要一份证明,本来在文件上盖上公章的印子就行了,如果把取公章的钥匙交给他,那么他就获得了不该有的权利。
3、计算内存容量
&&& 用运算符sizeof可以计算出数组的容量(字节数)。示例7-3-3(a)中,sizeof(a)的值是12(注意别忘了’/0’)。指针p指向a,但是sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。C++/C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。
注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。示例7-3-3(b)中,不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)。
&&& char a[] = &hello world&;
&&& char *p& =
&&& cout&& sizeof(a) &&&& // 12字节
&&& cout&& sizeof(p) &&&& // 4字节
示例7-3-3(a) 计算数组和指针的内存容量
&&& void Func(char a[100])
&&& &&& cout&& sizeof(a) &&&& // 4字节而不是100字节
示例7-3-3(b) 数组退化为指针
4、指针参数是如何传递内存的?
&&&&&& 如果函数的参数是一个指针,不要指望用该指针去申请动态内存。示例7-4-1中,Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?
void GetMemory(char *p, int num)
&&& p = (char *)malloc(sizeof(char) * num);
void Test(void)
&&& char *str = NULL;
&&& GetMemory(str, 100);&&& // str 仍然为 NULL&
&&& strcpy(str, &hello&);&& // 运行错误
示例7-4-1 试图用指针参数申请动态内存
毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。
如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见示例7-4-2。
void GetMemory2(char **p, int num)
&&& *p = (char *)malloc(sizeof(char) * num);
void Test2(void)
&&& char *str = NULL;
&&& GetMemory2(&str, 100);& // 注意参数是 &str,而不是str
&&& strcpy(str, &hello&);&&
&&& cout&& str &&
&&& free(str);&
示例7-4-2用指向指针的指针申请动态内存
由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单,见示例7-4-3。
char *GetMemory3(int num)
&&& char *p = (char *)malloc(sizeof(char) * num);
void Test3(void)
&&& char *str = NULL;
&&& str = GetMemory3(100);&
&&& strcpy(str, &hello&);
&&& cout&& str &&
&&& free(str);&
示例7-4-3 用函数返回值来传递动态内存
用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡,见示例7-4-4。
char *GetString(void)
&&& char p[] = &hello world&;
&&&&& // 编译器将提出警告
void Test4(void)
char *str = NULL;
str = GetString();& // str 的内容是垃圾
cout&& str &&
示例7-4-4 return语句返回指向“栈内存”的指针
用调试器逐步跟踪Test4,发现执行str = GetString语句后str不再是NULL指针,但是str的内容不是“hello world”而是垃圾。
如果把示例7-4-4改写成示例7-4-5,会怎么样?
char *GetString2(void)
&&& char *p = &hello world&;
void Test5(void)
&&& char *str = NULL;
&&& str = GetString2();
&&& cout&& str &&
示例7-4-5 return语句返回常量字符串
函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。
5、free和delete把指针怎么啦?
别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。
用调试器跟踪示例7-5,发现指针p被free以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p成了“野指针”。如果此时不把p设置为NULL,会让人误以为p是个合法的指针。
如果程序比较长,我们有时记不住p所指的内存是否已经被释放,在继续使用p之前,通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。
&&& char *p = (char *) malloc(100);
&&& strcpy(p, “hello”);
&&& free(p);&&& &&& // p 所指的内存被释放,但是p所指的地址仍然不变
&&& if(p != NULL)&& // 没有起到防错作用
&&& && strcpy(p, “world”);& // 出错
示例7-5& p成为野指针
6、内联函数的编程风格
&&& 关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前面不起任何作用。如下风格的函数Foo不能成为内联函数:
&&& inline void Foo(int x, int y); // inline仅与函数声明放在一起
&&& void Foo(int x, int y)
&&&&&&& …
而如下风格的函数Foo则成为内联函数:
&&& void Foo(int x, int y); &&&
&&& inline void Foo(int x, int y)&& // inline与函数定义体放在一起
&&&&&&& …
&&& 所以说,inline是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。一般地,用户可以阅读函数的声明,但是看不到函数的定义。尽管在大多数教科书中内联函数的声明、定义体前面都加了inline关键字,但我认为inline不应该出现在函数的声明中。这个细节虽然不会影响函数的功能,但是体现了高质量C++/C程序设计风格的一个基本原则:声明与定义不可混为一谈,用户没有必要、也不应该知道函数是否需要内联。
&&& 定义在类声明之中的成员函数将自动地成为内联函数,例如
&&& class A
&&&&&&& void Foo(int x, int y) { … } & // 自动地成为内联函数
将成员函数的定义体放在类声明之中虽然能带来书写上的方便,但不是一种良好的编程风格,上例应该改成:
&&& // 头文件
&&&&&&& void Foo(int x, int y);
&&& // 定义文件
&&& inline void A::Foo(int x, int y)
7、template
&& 函数模板 template &Class T& Max (T a,T b) 或者 template &Typename T& Max (T a,T b)
&& 类模板&& template &Class T&& class Person {
&&&&&&&&&&& int a,b,c;
&&&&&&&&&&& T
&&&&&&&&&&& };
STL实践指南  作者 Jeff Bogan
原作者姓名 Jeff Bogan
这是一篇指导您如何在Microsoft Visual Studio下学习STL并进行实践的文章。这篇文章从STL的基础知识讲起,循序渐进,逐步深入,涉及到了STL编写代码的方法、STL代码的编译和调试、命名空间(namespace)、STL中的ANSI / ISO字符串、各种不同类型的容器(container)、模板(template)、游标(Iterator)、算法(Algorithms)、分配器(Allocator)、容器的嵌套等方面的问题,作者在这篇文章中对读者提出了一些建议,并指出了使用STL时应该注意的问题。这篇文章覆盖面广,视角全面。不仅仅适合初学者学习STL,更是广大读者使用STL编程的实践指南。
这是一篇指导您如何在Microsoft Visual Studio下学习STL并进行实践的文章。这篇文章从STL的基础知识讲起,循序渐进,逐步深入,涉及到了STL编写代码的方法、STL代码的编译和调试、命名空间(namespace)、STL中的ANSI / ISO字符串、各种不同类型的容器(container)、模板(template)、游标(Iterator)、算法(Algorithms)、分配器(Allocator)、容器的嵌套等方面的问题,作者在这篇文章中对读者提出了一些建议,并指出了使用STL时应该注意的问题。这篇文章覆盖面广,视角全面。不仅仅适合初学者学习STL,更是广大读者使用STL编程的实践指南。
STL (标准模版库,Standard Template Library)是当今每个从事C++编程的人需要掌握的一项不错的技术。我觉得每一个初学STL的人应该花费一段时间来熟悉它,比如,学习STL时会有急剧升降的学习曲线,并且有一些命名是不太容易凭直觉就能够记住的(也许是好记的名字已经被用光了),然而如果一旦你掌握了STL,你就不会觉得头痛了。和MFC相比,STL更加复杂和强大。
STL有以下的一些优点:
可以方便容易地实现搜索数据或对数据排序等一系列的算法;
调试程序时更加安全和方便;
即使是人们用STL在UNIX平台下写的代码你也可以很容易地理解(因为STL是跨平台的)。
写这一部分是让一些初学计算机的读者在富有挑战性的计算机科学领域有一个良好的开端,而不必费力地了解那无穷无尽的行话术语和沉闷的规则,在这里仅仅把那些行话和规则当作STLer们用于自娱的创造品吧。
本文使用的代码在STL实践中主要具有指导意义。
一些基础概念的定义
模板(Template)——类(以及结构等各种数据类型和函数)的宏(macro)。有时叫做甜饼切割机(cookie cutter),正规的名称应叫做范型(generic)——一个类的模板叫做范型类(generic class),而一个函数的模板也自然而然地被叫做范型函数(generic function)。
STL——标准模板库,一些聪明人写的一些模板,现在已成为每个人所使用的标准C++语言中的一部分。
容器(Container)——可容纳一些数据的模板类。STL中有vector,set,map,multimap和deque等容器。
向量(Vector)——基本数组模板,这是一个容器。
游标(Iterator)——这是一个奇特的东西,它是一个指针,用来指向STL容器中的元素,也可以指向其它的元素。
Hello World程序
我愿意在我的黄金时间在这里写下我的程序:一个hello world程序。这个程序将一个字符串传送到一个字符向量中,然后每次显示向量中的一个字符。向量就像是盛放变长数组的花园,大约所有STL容器中有一半是基于向量的,如果你掌握了这个程序,你便差不多掌握了整个STL的一半了。
//程序:vector演示一
//目的:理解STL中的向量
// #include &stdafx.h& -如果你使用预编译的头文件就包含这个头文件
#include &vector&&&// STL向量的头文件。这里没有&.h&。
#include &iostream&&&// 包含cout对象的头文件。
&&//保证在程序中可以使用std命名空间中的成员。
char* szHW = &Hello World&;&&
//这是一个字符数组,以”/0”结束。
int main(int argc, char* argv[])
&&vector &char&&&//声明一个字符向量vector (STL中的数组)
&&//为字符数组定义一个游标iterator。
&&vector &char&::
&&//初始化字符向量,对整个字符串进行循环,
&&//用来把数据填放到字符向量中,直到遇到”/0”时结束。
&&char* cptr = szHW;&&// 将一个指针指向“Hello World”字符串
&&while (*cptr != '/0')
&&{&&vec.push_back(*cptr);&&cptr++;&&}
&&// push_back函数将数据放在向量的尾部。
&&// 将向量中的字符一个个地显示在控制台
&&for (vi=vec.begin(); vi!=vec.end(); vi++)&&
&&// 这是STL循环的规范化的开始——通常是 &!=& , 而不是 &&&
&&// 因为&&& 在一些容器中没有定义。
&&// begin()返回向量起始元素的游标(iterator),end()返回向量末尾元素的游标(iterator)。
&&{&&cout && *&&}&&// 使用运算符 “*” 将数据从游标指针中提取出来。
&&cout &&&&// 换行
&&return 0;
push_back是将数据放入vector(向量)或deque(双端队列)的标准函数。Insert是一个与之类似的函数,然而它在所有容器中都可以使用,但是用法更加复杂。end()实际上是取末尾加一(取容器中末尾的前一个元素),以便让循环正确运行——它返回的指针指向最靠近数组界限的数据。就像普通循环中的数组,比如for (i=0; i&6; i++) {ar[i] =} ——ar[6]是不存在的,在循环中不会达到这个元素,所以在循环中不会出现问题。
STL的烦恼之一——初始化
STL令人烦恼的地方是在它初始化的时候。STL中容器的初始化比C/C++数组初始化要麻烦的多。你只能一个元素一个元素地来,或者先初始化一个普通数组再通过转化填放到容器中。我认为人们通常可以这样做:
//程序:初始化演示
//目的:为了说明STL中的向量是怎样初始化的。
#include &cstring&&&// &cstring&和&string.h&相同
#include &vector&
int ar[10] = {&&12, 45, 234, 64, 12, 35, 63, 23, 12, 55&&};
char* str = &Hello World&;
int main(int argc, char* argv[])
&&vector &int& vec1(ar, ar+10);
&&vector &char& vec2(str, str+strlen(str));
&&return 0;
在编程中,有很多种方法来完成同样的工作。另一种填充向量的方法是用更加熟悉的方括号,比如下面的程序:
//程序:vector演示二
//目的:理解带有数组下标和方括号的STL向量
#include &cstring&
#include &vector&
#include &iostream&
char* szHW = &Hello World&;
int main(int argc, char* argv[])
&&vector &char& vec(strlen(sHW)); //为向量分配内存空间
&&int i, k = 0;
&&char* cptr = szHW;
&&while (*cptr != '/0')
&&{&&vec[k] = *&&cptr++;&&k++;&&}
&&for (i=0; i&vec.size(); i++)
&&{&&cout && vec[i];&&}
&&return 0;
这个例子更加清晰,但是对游标(iterator)的操作少了,并且定义了额外的整形数作为下标,而且,你必须清楚地在程序中说明为向量分配多少内存空间。
命名空间(Namespace)
与STL相关的概念是命名空间(namespace)。STL定义在std命名空间中。有3种方法声明使用的命名空间:
1.用using关键字使用这个命名空间,在文件的顶部,但在声明的头文件下面加入:
这对单个工程来说是最简单也是最好的方法,这个方法可以把你的代码限定在std命名空间中。
2.使用每一个模板前对每一个要使用的对象进行声明(就像原形化):
using std::
using std::
using std::
using std::
using std::
尽管这样写有些冗长,但可以对记忆使用的函数比较有利,并且你可以容易地声明并使用其他命名空间中的成员。
3.在每一次使用std命名空间中的模版时,使用std域标识符。比如:
typedef std::vector VEC_STR;
这种方法虽然写起来比较冗长,但是是在混合使用多个命名空间时的最好方法。一些STL的狂热者一直使用这种方法,并且把不使用这种方法的人视为异类。一些人会通过这种方法建立一些宏来简化问题。
除此之外,你可以把using namespace std加入到任何域中,比如可以加入到函数的头部或一个控制循环体中。
为了避免在调试模式(debug mode)出现恼人的警告,使用下面的编译器命令:
#pragma warning(disable: 4786)
另一条需要注意的是,你必须确保在两个尖括号之间或尖括号和名字之间用空格隔开,因为是为了避免同“&&”移位运算符混淆。比如
vector &list&int&&
这样写会报错,而这样写:
vector &list &int& &
就可以避免错误。
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&STL实践指南(中)
STL实践指南&&Practical Guide to STL
作者:Jeff Bogan
翻译:周翔
(接上篇)
另一种容器——集合(set)
这是微软帮助文档中对集合(set)的解释:“描述了一个控制变长元素序列的对象(注:set中的key和value是Key类型的,而map中的key和value是一个pair结构中的两个分量)的模板类,每一个元素包含了一个排序键(sort key)和一个值(value)。对这个序列可以进行查找、插入、删除序列中的任意一个元素,而完成这些操作的时间同这个序列中元素个数的对数成比例关系,并且当游标指向一个已删除的元素时,删除操作无效。”
而一个经过更正的和更加实际的定义应该是:一个集合(set)是一个容器,它其中所包含的元素的值是唯一的。这在收集一个数据的具体值的时候是有用的。集合中的元素按一定的顺序排列,并被作为集合中的实例。如果你需要一个键/值对(pair)来存储数据,map是一个更好的选择。一个集合通过一个链表来组织,在插入操作和删除操作上比向量(vector)快,但查找或添加末尾的元素时会有些慢。
下面是一个例子:
//程序:set演示
//目的:理解STL中的集合(set)
#include &string&
#include &set&
#include &iostream&
int main(int argc, char* argv[])
&&set &string&
&&set &string&::
&&strset.insert(&cantaloupes&);
&&strset.insert(&apple&);
&&strset.insert(&orange&);
&&strset.insert(&banana&);
&&strset.insert(&grapes&);
&&strset.insert(&grapes&);&&
&&for (si=strset.begin(); si!=strset.end(); si++)&&
&&{&&cout && *si && & &;&&}
&&return 0;
// 输出: apple banana cantaloupes grapes orange
//注意:输出的集合中的元素是按字母大小顺序排列的,而且每个值都不重复。
如果你感兴趣的话,你可以将输出循环用下面的代码替换:
copy(strset.begin(), strset.end(), ostream_iterator&string&(cout, & &));
.集合(set)虽然更强大,但我个人认为它有些不清晰的地方而且更容易出错,如果你明白了这一点,你会知道用集合(set)可以做什么。
所有的STL容器
容器(Container)的概念的出现早于模板(template),它原本是一个计算机科学领域中的一个重要概念,但在这里,它的概念和STL混合在一起了。下面是在STL中出现的7种容器:
vector(向量)——STL中标准而安全的数组。只能在vector 的“前面”增加数据。
deque(双端队列double-ended queue)——在功能上和vector相似,但是可以在前后两端向其中添加数据。
list(列表)——游标一次只可以移动一步。如果你对链表已经很熟悉,那么STL中的list则是一个双向链表(每个节点有指向前驱和指向后继的两个指针)。
set(集合)——包含了经过排序了的数据,这些数据的值(value)必须是唯一的。
map(映射)——经过排序了的二元组的集合,map中的每个元素都是由两个值组成,其中的key(键值,一个map中的键值必须是唯一的)是在排序或搜索时使用,它的值可以在容器中重新获取;而另一个值是该元素关联的数值。比如,除了可以ar[43] = &overripe&这样找到一个数据,map还可以通过ar[&banana&] = &overripe&这样的方法找到一个数据。如果你想获得其中的元素信息,通过输入元素的全名就可以轻松实现。
multiset(多重集)——和集合(set)相似,然而其中的值不要求必须是唯一的(即可以有重复)。
multimap(多重映射)——和映射(map)相似,然而其中的键值不要求必须是唯一的(即可以有重复)。
注意:如果你阅读微软的帮助文档,你会遇到对每种容器的效率的陈述。比如:log(n*n)的插入时间。除非你要处理大量的数据,否则这些时间的影响是可以忽略的。如果你发现你的程序有明显的滞后感或者需要处理时间攸关(time critical)的事情,你可以去了解更多有关各种容器运行效率的话题。
怎样在一个map中使用类?
Map是一个通过key(键)来获得value(值)的模板类。
另一个问题是你希望在map中使用自己的类而不是已有的数据类型,比如现在已经用过的int。建立一个“为模板准备的(template-ready)”类,你必须确保在该类中包含一些成员函数和重载操作符。下面的一些成员是必须的:
缺省的构造函数(通常为空)
拷贝构造函数
重载的”=”运算符
你应该重载尽可能多的运算符来满足特定模板的需要,比如,如果你想定义一个类作为 map中的键(key),你必须重载相关的运算符。但在这里不对重载运算符做过多讨论了。
//程序:映射自定义的类。
//目的:说明在map中怎样使用自定义的类。
#include &string&
#include &iostream&
#include &vector&
#include &map&
class CStudent
&&int nStudentID;
&&//缺省构造函数——通常为空
&&CStudent()&&{&&}
&&// 完整的构造函数
&&CStudent(int nSID, int nA)&&{&&nStudentID=nSID; nAge=nA;&&}
&&//拷贝构造函数
&&CStudent(const CStudent& ob)&&
&&&&{&&nStudentID=ob.nStudentID; nAge=ob.nA&&}
&&// 重载“=”
&&void operator = (const CStudent& ob)&&
&&&&{&&nStudentID=ob.nStudentID; nAge=ob.nA&&}
int main(int argc, char* argv[])
&&map &string, CStudent& mapS
&&mapStudent[&Joe Lennon&] = CStudent();
&&mapStudent[&Phil McCartney&] = CStudent();
&&mapStudent[&Raoul Starr&] = CStudent();
&&mapStudent[&Gordon Hamilton&] = CStudent();
&&// 通过姓名来访问Cstudent类中的成员
&&cout && &The Student number for Joe Lennon is & &&
&&&&(mapStudent[&Joe Lennon&].nStudentID) &&
&&return 0;
如果你喜欢使用typedef关键字,下面是个例子:
typedef set &int& SET_INT;
typedef SET_INT::iterator SET_INT_ITER
编写代码的一个习惯就是使用大写字母和下划线来命名数据类型。
ANSI / ISO字符串
ANSI/ISO字符串在STL容器中使用得很普遍。这是标准的字符串类,并得到了广泛地提倡,然而在缺乏格式声明的情况下就会出问题。你必须使用“&&”和输入输出流(iostream)代码(如dec, width等)将字符串串联起来。
可在必要的时候使用c_str()来重新获得字符指针。
&&&&&&&&&&&&&&&&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&STL实践指南(下)
STL实践指南&&Practical Guide to STL
作者:Jeff Bogan
翻译:周翔
(接中篇)
游标(Iterator)
我说过游标是指针,但不仅仅是指针。游标和指针很像,功能很像指针,但是实际上,游标是通过重载一元的”*”和”-&”来从容器中间接地返回一个值。将这些值存储在容器中并不是一个好主意,因为每当一个新值添加到容器中或者有一个值从容器中删除,这些值就会失效。在某种程度上,游标可以看作是句柄(handle)。通常情况下游标(iterator)的类型可以有所变化,这样容器也会有几种不同方式的转变:
iterator——对于除了vector以外的其他任何容器,你可以通过这种游标在一次操作中在容器中朝向前的方向走一步。这意味着对于这种游标你只能使用“++”操作符。而不能使用“--”或“+=”操作符。而对于vector这一种容器,你可以使用“+=”、“—”、“++”、“-=”中的任何一种操作符和“&”、“&=”、“&”、“&=”、“==”、“!=”等比较运算符。
reverse_iterator ——如果你想用向后的方向而不是向前的方向的游标来遍历除vector之外的容器中的元素,你可以使用reverse_iterator 来反转遍历的方向,你还可以用rbegin()来代替begin(),用rend()代替end(),而此时的“++”操作符会朝向后的方向遍历。
const_iterator ——一个向前方向的游标,它返回一个常数值。你可以使用这种类型的游标来指向一个只读的值。
const_reverse_iterator ——一个朝反方向遍历的游标,它返回一个常数值。
Set和Map中的排序
除了类型和值外,模板含有其他的参数。你可以传递一个回调函数(通常所说的声明“predicate”——这是带有一个参数的函数返回一个布尔值)。例如,如果你想自动建立一个集合,集合中的元素按升序排列,你可以用简明的方法建立一个set类:
set &int, greater&int& & set1
greater 是另一个模板函数(范型函数),当值放置在容器中后,它用来为这些值排序。如果你想按降序排列这些值,你可以这样写:
set &int, less&int& & set1
在实现算法时,将声明(predicate)作为一个参数传递到一个STL模板类中时会遇到很多的其他情况,下面将会对这些情况进行详细描述。
STL 的烦恼之二——错误信息
这些模板的命名需要对编译器进行扩充,所以当编译器因某种原因发生故障时,它会列出一段很长的错误信息,并且这些错误信息晦涩难懂。我觉得处理这样的难题没有什么好办法。但最好的方法是去查找并仔细研究错误信息指明代码段的尾端。还有一个烦恼就是:当你双击错误信息时,它会将错误指向模版库的内部代码,而这些代码就更难读了。一般情况下,纠错的最好方法是重新检查一下你的代码,运行时忽略所有的警告信息。
算法(Algorithms)
算法是模板中使用的函数。这才真正开始体现STL的强大之处。你可以学习一些大多数模板容器中都会用到的一些算法函数,这样你可以通过最简便的方式进行排序、查找、交换等操作。STL中包含着一系列实现算法的函数。比如:sort(vec.begin()+1, vec.end()-1)可以实现对除第一个和最后一个元素的其他元素的排序操作。
容器自身不能使用算法,但两个容器中的游标可以限定容器中使用算法的元素。既然这样,算法不直接受到容器的限制,而是通过采用游标,算法才能够得到支持。此外,很多次你会遇到传递一个已经准备好了的函数(以前提到的声明:predicate)作为参数,你也可以传递以前的旧值。
下面的例子演示了怎样使用算法:
//程序:测试分数统计
//目的:通过对向量中保存的分数的操作说明怎样使用算法
#include &algorithm&&&//如果要使用算法函数,你必须要包含这个头文件。
#include &numeric&&&// 包含accumulate(求和)函数的头文件
#include &vector&
#include &iostream&
int testscore[] = {67, 56, 24, 78, 99, 87, 56};
//判断一个成绩是否通过了考试
bool passed_test(int n)
&&return (n &= 60);
// 判断一个成绩是否不及格
bool failed_test(int n)
&&return (n & 60);
int main(int argc, char* argv[])
&&// 初始化向量,使之能够装入testscore数组中的元素
&&vector &int& vecTestScore(testscore,
&&&& testscore + sizeof(testscore) / sizeof(int));
&&vector &int&::
&&// 排序并显示向量中的数据
&&sort(vecTestScore.begin(), vecTestScore.end());
&&cout && &Sorted Test Scores:& &&
&&for (vi=vecTestScore.begin(); vi != vecTestScore.end(); vi++)
&&{&&cout && *vi && &, &;&&}
&&// 显示统计信息
&&// min_element 返回一个 _iterator_ 类型的对象,该对象指向值最小的那个元素。
&&//“*”运算符提取元素中的值。
&&vi = min_element(vecTestScore.begin(), vecTestScore.end());
&&cout && &The lowest score was & && *vi && &.& &&
&&//与min_element类似,max_element是选出最大值。
&&vi = max_element(vecTestScore.begin(), vecTestScore.end());
&&cout && &The highest score was & && *vi && &.& &&
&&// 使用声明函数(predicate function,指vecTestScore.begin()和vecTestScore.end())来确定通过考试的人数。
&&cout && count_if(vecTestScore.begin(), vecTestScore.end(), passed_test) &&
&&&&& out of & && vecTestScore.size() &&
&&&&& students passed the test& &&
&&// 确定有多少人考试挂了
&&cout && count_if(vecTestScore.begin(),
&&&&vecTestScore.end(), failed_test) &&
&&&&& out of & && vecTestScore.size() &&
&&&&& students failed the test& &&
&&//计算成绩总和
&&total = accumulate(vecTestScore.begin(),
&&&& vecTestScore.end(), 0);
&&// 计算显示平均成绩
&&cout && &Average score was & &&
&&&&(total / (int)(vecTestScore.size())) &&
&&return 0;
Allocator(分配器)
Allocator用在模板的初始化阶段,是为对象和数组进行分配内存空间和释放空间操作的模板类。它在各种情况下扮演着很神秘的角色,它关心的是高层内存的优化,而且对黑盒测试来说,使用Allocator是最好的选择。通常,我们不需要明确指明它,因为它们通常是作为不用添加的缺省的参数出现的。如果在专业的测试工作中出现了Allocator,你最好搞清楚它是什么。
Embed Templates(嵌入式模版)和Derive Templates(基模板)
每当你使用一个普通的类的时候,你也可以在其中使用一个STL类。它是可以被嵌入的:
class CParam
&&vector &double& vecD
或者将它作为一个基类:
class CParam : public vector &double&
STL模版类作为基类时需要谨慎。这需要你适应这种编程方式。
模版中的模版
为构建一个复杂的数据结构,你可以将一个模板植入另一个模板中(即“模版嵌套”)。一般最好的方法是在程序前面使用typedef关键字来定义一个在另一个模板中使用的模版类型。
// 程序:在向量中嵌入向量的演示。
//目的:说明怎样使用嵌套的STL容器。
#include &iostream&
#include &vector&
typedef vector &int& VEC_INT;
int inp[2][2] = {{1, 1}, {2, 0}};&&
&&// 要放入模板中的2x2的正则数组
int main(int argc, char* argv[])
&&vector &VEC_INT&
&&// 如果你想用一句话实现这样的嵌套,你可以这样写:
&&// vector &vector &int& &
&&// 将数组填入向量
&&VEC_INT v0(inp[0], inp[0]+2);&&
&&&&// 传递两个指针
&&&&// 将数组中的值拷贝到向量中
&&VEC_INT v1(inp[1], inp[1]+2);
&&vecvec.push_back(v0);
&&vecvec.push_back(v1);
&&for (i=0; i&2; i++)
&&&&for (j=0; j&2; j++)
&&&&&&cout && vecvec[i][j] && &&&&;
&&&&cout &&
&&return 0;
虽然在初始化时很麻烦,一旦你将数据填如向量中,你就实现了一个变长的可扩充的二维数组(大小可扩充直到使用完内存)。根据实际需要,可以使用各种容器的嵌套组合。
STL是有用的,但是使用过程中的困难和麻烦是再所难免的。就像中国人所说的:“如果你掌握了它,便犹如虎添翼。”
堆:顺序随意
栈:先进后出
堆和栈的区别
一、预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1)、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
2)、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3)、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4)、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5)、程序代码区—存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
&&&&& char s[] = &abc&; 栈
&&&&& char *p2; 栈
&&&&& char *p3 = &123456&; 在常量区,p3在栈上。
&&&&& static int c =0; 全局(静态)初始化区
&&&&& p1 = (char *)malloc(10);
&&&&& p2 = (char *)malloc(20);
&&&&& 分配得来得10和20字节的区域就在堆区。
&&&&& strcpy(p1, &123456&); 放在常量区,编译器可能会将它与p3所指向的&123456&优化成一个地方。
二、堆和栈的理论知识
2.1)申请方式
stack:由系统自动分配。 例如,声明在函数中一个局部变量 系统自动在栈中为b开辟空间
heap:需要程序员自己申请,并指明大小,在c中malloc函数如p1 = (char *)malloc(10);在C++中用new运算符如p2 = (char *)malloc(10);但是注意p1、p2本身是在栈中的
2.2)申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
2.3)申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4)申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便. 另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活
2.5)堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
2.6)存取效率的比较
char s1[] = &aaaaaaaaaaaaaaa&;
char *s2 = &bbbbbbbbbbbbbbbbb&;
aaaaaaaaaaa是在运行时刻赋值的;而bbbbbbbbbbb是在编译时就确定的;但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
void main()
&&&&& char a = 1;&&&&&
&&&&& char c[] = &&;&&&&&
&&&&& char *p =&&;&&&&
&&&&& a = c[1];&&&&&
&&&&& a = p[1];&&&&&
&&&&&&&&&&
2.7)小结:
堆和栈的区别可以用如下的比喻来看出:使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
堆和栈的区别主要分:
操作系统方面的堆和栈,如上面说的那些,不多说了。还有就是数据结构方面的堆和栈,这些都是不同的概念。这里的堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是满足先进后出的性质的数学或数据结构。虽然堆栈,堆栈的说法是连起来叫,但是他们还是有很大区别的,连着叫只是由于历史的原因。
10、C、C++的一些基本知识要点。
C++/C试题
&&&&&& 本试题仅用于考查C++/C程序员的基本编程技能。内容限于C++/C常用语法,不涉及数据结构、算法以及深奥的语法。考试成绩能反映出考生的编程质量以及对C++/C的理解程度,但不能反映考生的智力和软件开发能力。
一、请填写BOOL , float, 指针变量 与“零值”比较的 if 语句。(10分)
提示:这里“零值”可以是0, 0.0 , FALSE或者“空指针”。例如 int 变量 n 与“零值”比较的 if 语句为:
&&& if ( n == 0 )
&&& if ( n != 0 )
以此类推。
请写出 BOOL& flag 与“零值”比较的 if 语句:
请写出 float& x 与“零值”比较的 if 语句:
请写出 char &*p 与“零值”比较的 if 语句:
二、以下为Windows NT下的32位C++程序,请计算sizeof的值(10分)
&&&&&& char& str[] = “Hello” ;
&&&&&& char&& *p =
int&&&& n = 10;
sizeof (str ) =&&&&&&&
sizeof ( p ) =&&&&&&&
&&& &&&&&&
sizeof ( n ) =
void Func ( char str[100])
&sizeof( str ) =&&&
void *p = malloc( 100 );
sizeof ( p ) =
三、简答题(25分)
1、头文件中的 ifndef/define/endif 干什么用?
2、#include& &filename.h&&& 和& #include& “filename.h” 有什么区别?
3、const 有什么用途?(请至少说明两种)
4、在C++ 程序中调用被 C编译器编译后的函数,为什么要加 extern “C”声明?
5、请简述以下两个for循环的优缺点
for (i=0; i&N; i++)
if (condition)
&&& DoSomething();
&&& DoOtherthing();
if (condition)
for (i=0; i&N; i++)
&&& DoSomething();
&&& for (i=0; i&N; i++)
&&& DoOtherthing();
四、有关内存的思考题(20分)
void GetMemory(char *p)
p = (char *)malloc(100);
void Test(void)
char *str = NULL;
GetMemory(str);&&
strcpy(str, &hello world&);
printf(str);
请问运行Test函数会有什么样的结果?
char *GetMemory(void)
char p[] = &hello world&;
void Test(void)
char *str = NULL;
str = GetMemory();&&&
printf(str);
请问运行Test函数会有什么样的结果?
Void GetMemory2(char **p, int num)
*p = (char *)malloc(num);
void Test(void)
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, &hello&);&&
printf(str);&&&
请问运行Test函数会有什么样的结果?
void Test(void)
char *str = (char *) malloc(100);
&&& strcpy(str, “hello”);
&&& free(str);& &&&
&&& if(str != NULL)
&&& & strcpy(str, “world”);
printf(str);
请问运行Test函数会有什么样的结果?
五、编写strcpy函数(10分)
已知strcpy函数的原型是
&&&&&& char *strcpy(char *strDest, const char *strSrc);
&&&&&& 其中strDest是目的字符串,strSrc是源字符串。
(1)不调用C++/C的字符串库函数,请编写函数 strcpy
(2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?
六、编写类String的构造函数、析构函数和赋值函数(25分)
已知类String的原型为:
&&& class String
&&& & public:
&&&&&&& String(const char *str = NULL); // 普通构造函数
&&&&&&& String(const String &other);&&& &&& // 拷贝构造函数
&&&&&&& ~ String(void);&&&&&&&&&&&& &&& &&& // 析构函数
&&&&&&& String & operate =(const String &other);&&& // 赋值函数
&&& & private:
&&&&&&& char& & *m_&&&&&&& &&&&&&& // 用于保存字符串
&&&&&& 请编写String的上述4个函数。
:C++/C试题的答案与评分标准
一、请填写BOOL , float, 指针变量 与“零值”比较的 if 语句。(10分)
请写出 BOOL& flag 与“零值”比较的 if 语句。(3分)
标准答案:
&&& if ( flag )
&&& if ( !flag )
如下写法均属不良风格,不得分。
&&& if (flag == TRUE)&&
&&& if (flag == 1 ) &&&
&&& if (flag == FALSE) &
&&& &&& if (flag == 0)& &&&
请写出 float& x 与“零值”比较的 if 语句。(4分)
标准答案示例:
const float EPSINON = 0.00001;
if ((x &= - EPSINON) && (x &= EPSINON)
不可将浮点变量用“==”或“!=”与数字比较,应该设法转化成“&=”或“&=”此类形式。
如下是错误的写法,不得分。
&&& if (x == 0.0)&&
&&& if (x != 0.0)&& &&&
请写出 char &*p 与“零值”比较的 if 语句。(3分)
标准答案:
&&& if (p == NULL)
&&& if (p != NULL)
如下写法均属不良风格,不得分。
&&& if (p == 0)
&&& if (p != 0) &&&
&&& if (p) &
&&& &&& if (!)& &&&
二、以下为Windows NT下的32位C++程序,请计算sizeof的值(10分)
&&&&&& char& str[] = “Hello” ;
&&&&&& char&& *p =
int&&&& n = 10;
sizeof (str ) =& 6&& (2分)
sizeof ( p ) =&& 4&& (2分)
&&& &&&&&&
sizeof ( n ) =&& 4&& (2分)
void Func ( char str[100])
&sizeof( str ) =&& 4 &&&&(2分)
void *p = malloc( 100 );
sizeof ( p ) =& 4&&&&& (2分)
三、简答题(25分)
1、头文件中的 ifndef/define/endif 干什么用?(5分)
答:防止该头文件被重复引用。
2、#include& &filename.h&&& 和& #include& “filename.h” 有什么区别?(5分)
答:对于#include& &filename.h& ,编译器从标准库路径开始搜索 filename.h
&&& 对于#include& “filename.h” ,编译器从用户的工作路径开始搜索 filename.h
3、const 有什么用途?(请至少说明两种)(5分)
答:(1)可以定义 const 常量
(2)const可以修饰函数的参数、返回值,甚至函数的定义体。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
4、在C++ 程序中调用被 C编译器编译后的函数,为什么要加 extern “C”? (5分)
答:C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为: void foo(int x, int y);
该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。
C++提供了C连接交换指定符号extern“C”来解决名字匹配问题。
5、请简述以下两个for循环的优缺点(5分)
for (i=0; i&N; i++)
if (condition)
&&& DoSomething();
&&& DoOtherthing();
if (condition)
for (i=0; i&N; i++)
&&& DoSomething();
&&& for (i=0; i&N; i++)
&&& DoOtherthing();
优点:程序简洁
缺点:多执行了N-1次逻辑判断,并且打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。
优点:循环的效率高
缺点:程序不简洁
四、有关内存的思考题(每小题5分,共20分)
void GetMemory(char *p)
p = (char *)malloc(100);
void Test(void)
char *str = NULL;
GetMemory(str);&&
strcpy(str, &hello world&);
printf(str);
请问运行Test函数会有什么样的结果?
答:程序崩溃。
因为GetMemory并不能传递动态内存,
Test函数中的 str一直都是 NULL。
strcpy(str, &hello world&);将使程序崩溃。
char *GetMemory(void)
char p[] = &hello world&;
void Test(void)
char *str = NULL;
str = GetMemory();&&&
printf(str);
请问运行Test函数会有什么样的结果?
答:可能是乱码。
因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。
void GetMemory2(char **p, int num)
*p = (char *)malloc(num);
void Test(void)
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, &hello&);&&
printf(str);&&&
请问运行Test函数会有什么样的结果?
(1)能够输出hello
(2)内存泄漏
void Test(void)
char *str = (char *) malloc(100);
&&& strcpy(str, “hello”);
&&& free(str);& &&&
&&& if(str != NULL)
&&& & strcpy(str, “world”);
printf(str);
请问运行Test函数会有什么样的结果?
答:篡改动态内存区的内容,后果难以预料,非常危险。
因为free(str);之后,str成为野指针,
if(str != NULL)语句不起作用。
五、编写strcpy函数(10分)
已知strcpy函数的原型是
&&&&&& char *strcpy(char *strDest, const char *strSrc);
&&&&&& 其中strDest是目的字符串,strSrc是源字符串。
(1)不调用C++/C的字符串库函数,请编写函数 strcpy
char *strcpy(char *strDest, const char *strSrc);
&&& assert((strDest!=NULL) && (strSrc !=NULL)); // 2分
&&& char *address = strD&&&&&&&&&&&&&&&&&& // 2分
&&& while( (*strDest++ = * strSrc++) != ‘/0’ )&&& // 2分
&&&&&& NULL ;
&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 2分
(2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?
答:为了实现链式表达式。&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 2分
例如&&&&&& int length = strlen( strcpy( strDest, “hello world”) );
六、编写类String的构造函数、析构函数和赋值函数(25分)
已知类String的原型为:
&&& class String
&&& & public:
&&&&&&& String(const char *str = NULL); // 普通构造函数
&&&&&&& String(const String &other);&&& &&& // 拷贝构造函数
&&&&&&& ~ String(void);&&&&&&&&&&&& &&& &&& // 析构函数
&&&&&&& String & operate =(const String &other);&&& // 赋值函数
&&& & private:
&&&&&&& char& & *m_&&&&&&& &&& &&& // 用于保存字符串
&&&&&& 请编写String的上述4个函数。
标准答案:
// String的析构函数
&&&&&& String::~String(void)&&&&&&&&&&&&&& // 3分
&&& delete [] m_ &&&&&&&&&&&&&&&&&&&&
// 由于m_data是内部数据类型,也可以写成 delete m_
&&&&&& // String的普通构造函数&&&&&&&&&&&&
&&&&&& String::String(const char *str)&&&&& // 6分
&&& if(str==NULL)&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&& m_data = new char[1];&&& // 若能加 NULL 判断则更好
&&&&&& *m_data = ‘/0’;&&&&&&&&&&&&&&&&&&&&&
&&& }&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&&& int length = strlen(str);&&&&&&&&&&
&&&&&& m_data = new char[length+1];& // 若能加 NULL 判断则更好&&&&&
&&&&&& strcpy(m_data, str);&&&&&&&&&&&&&&&
// 拷贝构造函数
&&& String::String(const String &other)&& // 3分
&&& int length = strlen(other.m_data);
&&& m_data = new char[length+1];&&&&& // 若能加 NULL 判断则更好&&&
&&& strcpy(m_data, other.m_data);&&&&&&&&
// 赋值函数
&&& String & String::operate =(const String &other)&&& // 13分
&&&&&& // (1) 检查自赋值&&&&&&&&&&&&&&&&&&&& // 4分
&&&&&& if(this == &other)
&&&&&&&&&& return *
// (2) 释放原有的内存资源&&&&&&&&&&& // 3分
&&&&&& delete [] m_
&&&&&& // (3)分配新的内存资源,并复制内容 // 3分
&&& int length = strlen(other.m_data);
&&& m_data = new char[length+1];&&&&&&&& // 若能加 NULL 判断则更好
&&& &&& strcpy(m_data, other.m_data);
&&&&&& // (4)返回本对象的引用&&&&&&&&&&& // 3分
&&&&&& return *
C++题目汇总
1.求下面函数的返回值(微软)
int func(x)
int countx = 0;
countx ++;
x = x&(x-1);
假定x = 9999。 答案:8
思路:将x转化为2进制,看含有的1的个数。
2. 什么是“引用”?申明和使用“引用”要注意哪些问题?
答:引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。
<span style="font-size:14 color:#. 将“引用”作为函数参数有哪些特点?
(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用&*指针变量名&的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
4. 在什么时候需要使用“常引用”? 
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;
const int &ra=a;
ra=1; //错误
a=1; //正确
string foo( );
void bar(string & s);
那么下面的表达式将是非法的:
bar(foo( ));
bar(&hello world&);
原因在于foo( )和&hello world&串都会产生一个临时对象,而在C&#43;&#43;中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
引用型参数应该在能被定义为const的情况下,尽量定义为const 。
<span style="font-size:14 color:#. 将“引用”作为函数返回&#20540;类型的&#26684;式、好处和需要遵守的规则?
&#26684;式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }
好处:在内存中不产生被返回&#20540;的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
注意事项:
(1)不能返回局部变量的引用。这条可以参照Effective C&#43;&#43;[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了&无所指&的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C&#43;&#43;[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
(3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C&#43;&#43;[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋&#20540;常常与某些其它属性或者对象的状态有关,因此有必要将赋&#20540;操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋&#20540;就会破坏业务规则的完整性。
(4)流操作符重载返回&#20540;申明为“引用”的作用:
流操作符&&和&&,这两个操作符常常希望被连续使用,例如:cout && &hello& && 因此这两个操作符的返回&#20540;应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个&&操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用&&操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C&#43;&#43;语言中引入引用这个概念的原因吧。赋&#20540;操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x
= j = 10;或者(x=10)=100;赋&#20540;操作符的返回&#20540;必须是一个左&#20540;,以便可以被继续赋&#20540;。因此引用成了这个操作符的惟一返回&#20540;选择。
#i nclude &iostream.h&
int &put(int n);
int vals[10];
int error=-1;
void main()
put(0)=10; //以put(0)函数&#20540;作为左&#20540;,等价于vals[0]=10;
put(9)=20; //以put(9)函数&#20540;作为左&#20540;,等价于vals[9]=20;
cout&&vals[0];
cout&&vals[9];
int &put(int n)
if (n&=0 && n&=9 ) return vals[n];
else { cout&&&subscript error&; }
(5)在另外的一些操作符中,却千万不能返回引用:&#43;-*/ 四则运算符。它们不能返回引用,Effective C&#43;&#43;[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回&#20540;,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回&#20540;的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a&#43;b) == (c&#43;d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。
6. “引用”与多态的关系?
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
Class A; Class B : Class A{...}; B A& ref =
7. “引用”与指针的区别是什么?
指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传ref和pointer的区别。
8. 什么时候需要“引用”?
流操作符&&和&&、赋&#20540;操作符=的返回&#20540;、拷贝构造函数的参数、赋&#20540;操作符=的参数、其它情况都推荐使用引用。
以上 2-8 参考:http://blog.csdn.net/wfwd/archive//763551.aspx
9. 结构与联合有和区别?
<span style="color:#. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
2. 对于联合的不同成员赋&#20540;, 将会对其它成员重写, 原来成员的&#20540;就不存在了, 而对于结构的不同成员赋&#20540;是互不影响的。
10. 下面关于“联合”的题目的输出?
#i nclude &stdio.h&
char x[2];
void main()
a.x[0] = 10;
a.x[1] = 1;
printf(&%d&,a.i);
答案:266 (低位低地址,高位高地址,内存占用情况是Ox010A)
union{ /*定义一个联合*/
struct{ /*在联合中定义一个结构*/
number.i=0x4241; /*联合成员赋&#20540;*/
printf(&%c%c/n&, number.half.first, mumber.half.second);
number.half.first='a'; /*联合中结构成员赋&#20540;*/
number.half.second='b';
printf(&%x/n&, number.i);
答案: AB (0x41对应'A',是低位;Ox42对应'B',是高位)
6261 (number.i和number.half共用一块地址空间)
11. 已知strcpy的函数原型:char *strcpy(char *strDest, const char *strSrc)其中strDest 是目的字符串,strSrc 是源字符串。不调用C&#43;&#43;/C 的字符串库函数,请编写函数 strcpy。
char *strcpy(char *strDest, const char *strSrc)
if ( strDest == NULL || strSrc == NULL)
return NULL ;
if ( strDest == strSrc)
return strD
char *tempptr = strD
while( (*strDest&#43;&#43; = *strSrc&#43;&#43;) != ‘/0’)
12. 已知String类定义如下:
class String
String(const char *str = NULL); // 通用构造函数
String(const String &another); // 拷贝构造函数
~ String(); // 析构函数
String & operater =(const String &rhs); // 赋&#20540;函数
char *m_ // 用于保存字符串
尝试写出类的成员函数实现。
String::String(const char *str)
if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断
m_data = new char[1] ;
m_data[0] = '/0' ;
m_data = new char[strlen(str) &#43; 1];
strcpy(m_data,str);
String::String(const String &another)
m_data = new char[strlen(another.m_data) &#43; 1];
strcpy(m_data,other.m_data);
String& String::operator =(const String &rhs)
if ( this == &rhs)
delete []m_ //删除原来的数据,新开一块内存
m_data = new char[strlen(rhs.m_data) &#43; 1];
strcpy(m_data,rhs.m_data);
String::~String()
delete []m_
13. .h头文件中的ifndef/define/endif 的作用?
答:防止该头文件被重复引用。
<span style="font-size:14 color:#. #i nclude&file.h& 与 #i nclude &file.h&的区别?
答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。
15.在C&#43;&#43; 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?
首先,作为extern是C/C&#43;&#43;语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数
extern &C&是连接申明(linkage declaration),被extern &C&修饰的变量和函数是按照C语言方式编译和连接的,来看看C&#43;&#43;中对类&#20284;C的函数是怎样编译的:
作为一种面向对象的语言,C&#43;&#43;支持函数重载,而过程式语言C则不支持。函数被C&#43;&#43;编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C编译器编译后在符号库中的名字为_foo,而C&#43;&#43;编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。
_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C&#43;&#43;就是靠这种机制来实现函数重载的。例如,在C&#43;&#43;中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C&#43;&#43;中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以&.&来区分。而本质上,编译器在进行编译时,与函数的处理相&#20284;,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
未加extern &C&声明时的连接方式
假设在C&#43;&#43;中,模块A的头文件如下:
// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif  
在模块B中引用该函数:
// 模块B实现文件 moduleB.cpp
#i nclude &moduleA.h&
实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!
加extern &C&声明后的编译和连接方式
加extern &C&声明后,模块A的头文件变为:
// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern &C& int foo( int x, int y );
#endif  
在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:
(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。
如果在模块A中函数声明了foo为extern &C&类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。
所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C&#43;&#43;与C及其它语言的混合编程。  
明白了C&#43;&#43;中extern &C&的设立动机,我们下面来具体分析extern &C&通常的使用技巧:
extern &C&的惯用法
(1)在C&#43;&#43;中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:
extern &C&
#i nclude &cExample.h&
而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern &C&声明,在.c文件中包含了extern &C&时会出现编译语法错误。
C&#43;&#43;引用C函数例子工程中包含的三个文件的源代码如下:
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
/* c语言实现文件:cExample.c */
#i nclude &cExample.h&
int add( int x, int y )
return x &#43;
// c&#43;&#43;实现文件,调用add:cppFile.cpp
extern &C&
#i nclude &cExample.h&
int main(int argc, char* argv[])
如果C&#43;&#43;调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern &C& { }。
(2)在C中引用C&#43;&#43;语言中的函数和变量时,C&#43;&#43;的头文件需添加extern &C&,但是在C语言中不能直接引用声明了extern &C&的该头文件,应该仅将C文件中将C&#43;&#43;中定义的extern &C&函数声明为extern类型。
C引用C&#43;&#43;函数例子工程中包含的三个文件的源代码如下:
//C&#43;&#43;头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern &C& int add( int x, int y );
//C&#43;&#43;实现文件 cppExample.cpp
#i nclude &cppExample.h&
int add( int x, int y )
return x &#43;
/* C实现文件 cFile.c
/* 这样会编译出错:#i nclude &cExample.h& */
extern int add( int x, int y );
int main( int argc, char* argv[] )
add( 2, 3 );
<span style="color:#题目的解答请参考《C&#43;&#43;中extern “C”含义深层探索》注解:
16. 关联、聚合(Aggregation)以及组合(Composition)的区别?
涉及到UML中的一些概念:关联是表示两个类的一般性联系,比如“学生”和“老师”就是一种关联关系;聚合表示has-a的关系,是一种相对松散的关系,聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系:
从实现的角度讲,聚合可以表示为:
class A {...} class B { A* .....}
而组合表示contains-a的关系,关联性强于聚合:组合类与被组合类有相同的生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系:
实现的形式是:
class A{...} class B{ A ...}
参考文章:http://blog.csdn.net/wfwd/archive//763753.aspx
http://blog.csdn.net/wfwd/archive//763760.aspx
17.面向对象的三个基本特征,并简单叙述之?
<span style="color:#. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)
2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=&接口继承以及纯虚函数)构成了功能复用的两种方式。
3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋&#20540;之后,父对象就可以根据当前赋&#20540;给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋&#20540;给父类类型的指针。
<span style="font-size:14 color:#. 重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?
常考的题目。从定义上来说:
重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
重写:是指子类重新定义复类虚函数的方法。
从实现原理上来说:
重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):和function func(p:string):。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
19. 多态的作用?
主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。
20. Ado与Ado.net的相同与不同?
除了“能够让应用程序处理存储于DBMS 中的数据“这一基本相&#20284;点外,两者没有太多共同之处。但是Ado使用OLE DB 接口并基于微软的COM 技术,而ADO.NET 拥有自己的ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于COM 体系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,这也就是说ADO.NET 和ADO是两种数据访问方式。ADO.net 提供对XML 的支持。
21. New delete 与malloc free 的联系与区别?
答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.
22. #define DOUBLE(x) x&#43;x ,i = 5*DOUBLE(5); i 是多少?
答案:i 为30。
23. 有哪几种情况只能用intialization list 而不能用assignment?
答案:当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。
24. C&#43;&#43;是不是类型安全的?
答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
<span style="font-size:14 color:#. main 函数执行以前,还会执行什么代码?
答案:全局对象的构造函数会在main 函数之前执行。
<span style="font-size:14 color:#. 描述内存分配方式以及它们的区别?
<span style="color:#) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
27.struct 和 class 的区别
答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和 class 在其他方面是功能相当的。
从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结构仅仅象一堆缺乏封装和功能的开放的内存位,而类就象活的并且可靠的社会成员,它有智能服务,有牢固的封装屏障和一个良好定义的接口。既然大多数人都这么认为,那么只有在你的类有很少的方法并且有公有数据(这种事情在良好设计的系统中是存在的!)时,你也许应该使用 struct 关键字,否则,你应该使用 class 关键字。
<span style="font-size:12 color:#.当一个类A 中没有生命任何成员变量与成员函数,这时sizeof(A)的&#20540;是多少,如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk)
答案:肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。
29. 在8086 汇编下,逻辑地址和物理地址是怎样转换的?(Intel)
答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H&#43;通用寄存器内地址,就得到了真正要访问的地址。
<span style="font-size:12 color:#. 比较C&#43;&#43;中的4种类型转换方式?
请参考:http://blog.csdn.net/wfwd/archive//763785.aspx,重点是static_cast, dynamic_cast和reinterpret_cast的区别和应用。
31.分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。
BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a & EXP && a &-EXP)
pointer : if ( a != NULL) or if(a == NULL)
32.请说出const与#define 相比,有何优点?
答案:1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。
33.简述数组与指针的区别?
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(1)修改内容上的差别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C&#43;&#43;/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
char a[] = &hello world&;
cout&& sizeof(a) && // 12 字节
cout&& sizeof(p) && // 4 字节
计算数组和指针的内存容量
void Func(char a[100])
cout&& sizeof(a) && // 4 字节而不是100 字节
34.类成员函数的重载、覆盖和隐藏区别?
a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
35. There are two int variables: a and b, don’t use “if”, “? :”, “switch”or other judgement statements, find out the biggest one of the two numbers.
答案:( ( a &#43; b ) &#43; abs( a - b ) ) / 2
36. 如何打印出当前源文件的文件名以及源文件的当前行号?
cout && __FILE__ ;
cout&&__LINE__ ;
__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。
37. main 主函数执行完毕后,是否可能会再执行一段代码,给出说明?
答案:可以,可以用_onexit 注册一个函数,它会在main 之后执行int fn1(void), fn2(void), fn3(void), fn4 (void);
void main( void )
String str(&zhanglin&);
_onexit( fn1 );
_onexit( fn2 );
_onexit( fn3 );
_onexit( fn4 );
printf( &This is executed first./n& );
printf( &next./n& );
printf( &executed & );
printf( &is & );
printf( &This & );
The _onexit function is passed the address of a function (func) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out) order. The functions passed to _onexit
cannot take parameters.
38. 如何判断一段程序是由C 编译程序还是由C&#43;&#43;编译程序编译的?
#ifdef __cplusplus
cout&&&c&#43;&#43;&;
cout&&&c&;
39.文件中有一组整数,要求排序后输出到另一个文件中
#i nclude&iostream&
#i nclude&fstream&
void Order(vector&int&& data) //bubble sort
int count = data.size() ;
int tag = // 设置是否需要继续冒泡的标志位
for ( int i = 0 ; i & i&#43;&#43;)
for ( int j = 0 ; j & count - i - 1 ; j&#43;&#43;)
if ( data[j] & data[j&#43;1])
int temp = data[j] ;
data[j] = data[j&#43;1] ;
data[j&#43;1] =
if ( !tag )
void main( void )
vector&int&
ifstream in(&c://data.txt&);
cout&&&file error!&;
while (!in.eof())
data.push_back(temp);
in.close(); //关闭输入文件流
Order(data);
ofstream out(&c://result.txt&);
if ( !out)
cout&&&file error!&;
for ( i = 0 ; i & data.size() ; i&#43;&#43;)
out&&data[i]&&& &;
out.close(); //关闭输出文件流
40. 链表题:一个链表的结点结构
struct Node
typedef struct Node N
(1)已知链表的头结点head,写一个函数把这个链表逆序 ( Intel)
Node * ReverseList(Node *head) //链表逆序
if ( head == NULL || head-&next == NULL )
Node *p1 =
Node *p2 = p1-&
Node *p3 = p2-&
p1-&next = NULL ;
while ( p3 != NULL )
p2-&next = p1 ;
p2-&next = p1 ;
head = p2 ;
(2)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序。(保留所有结点,即便大小相同)
Node * Merge(Node *head1 , Node *head2)
if ( head1 == NULL)
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
Node *p1 = NULL;
Node *p2 = NULL;
if ( head1-&data & head2-&data )
head = head1 ;
p1 = head1-&
p2 = head2 ;
head = head2 ;
p2 = head2-&
p1 = head1 ;
Node *pcurrent =
while ( p1 != NULL && p2 != NULL)
if ( p1-&data &= p2-&data )
pcurrent-&next = p1 ;
pcurrent = p1 ;
pcurrent-&next = p2 ;
pcurrent = p2 ;
if ( p1 != NULL )
pcurrent-&next = p1 ;
if ( p2 != NULL )
pcurrent-&next = p2 ;
(3)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。 (Autodesk)
Node * MergeRecursive(Node *head1 , Node *head2)
if ( head1 == NULL )
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
if ( head1-&data & head2-&data )
head = head1 ;
head-&next = MergeRecursive(head1-&next,head2);
head = head2 ;
head-&next = MergeRecursive(head1,head2-&next);
41. 分析一下这段程序的输出 (Autodesk)
cout&&&default constructor&&&
cout&&&destructed&&&
B(int i):data(i) //B(int) works as a converter ( int -& instance of B)
cout&&&constructed by parameter & && data &&
B Play( B b)
(1) results:
int main(int argc, char* argv[]) constructed by parameter 5
{ destructed B(5)形参析构
B t1 = Play(5); B t2 = Play(t1);   destructed t1形参析构
return 0;               destructed t2 注意顺序!
} destructed t1
(2) results:
int main(int argc, char* argv[]) constructed by parameter 5
{ destructed B(5)形参析构
B t1 = Play(5); B t2 = Play(10);   constructed by parameter 10
return 0;               destructed B(10)形参析构
} destructed t2 注意顺序!
destructed t1
42. 写一个函数找出一个整数数组中,第二大的数 (microsoft)
const int MINNUMBER = -32767 ;
int find_sec_max( int data[] , int count)
int maxnumber = data[0] ;
int sec_max = MINNUMBER ;
for ( int i = 1 ; i & i&#43;&#43;)
if ( data[i] & maxnumber )
maxnumber = data[i] ;
if ( data[i] & sec_max )
sec_max = data[i] ;
return sec_
43. 写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。
KMP算法效率最好,时间复杂度是O(n&#43;m)。
44. 多重继承的内存分配问题:
比如有class A : public class B, public class C {}
那么A的内存结构大致是怎么样的?
这个是compiler-dependent的, 不同的实现其细节可能不同。
如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。
可以参考《深入探索C&#43;&#43;对象模型》,或者:
http://blog.csdn.net/wfwd/archive//763797.aspx
45. 如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针)
struct node { node*}
bool check(const node* head) {} //return false : 无环;true: 有环
一种O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然):
bool check(const node* head)
if(head==NULL)
node *low=head, *fast=head-&
while(fast!=NULL && fast-&next!=NULL)
fast=fast-&next-&
if(low==fast)
在一些公司面试中,数据结构与算法一般都是大公司都会考的题目,而小公司考得很少。考试题目一般集中排序算法与时间复杂度、链表结构的应用。
一、排序算法与复杂度
常用排序算法的时间复杂度和空间复杂度
最差时间分析
平均时间复杂度
空间复杂度
O(n*log2n)
O(log2n)~O(n)
二叉树排序
O(n*log2n)
O(n*log2n)
O(n*log2n)
1、时间复杂度
(1)时间频度一个算法执行所耗费的时间,从理论上是不能算出来的,必须上机运行测试才能知道。但我们不可能也没有必要对每个算法都上机测试,只需知道哪个算法花费的时间多,哪个算法花费的时间少就可以了。并且一个算法花费的时间与算法中语句的执行次数成正比例,哪个算法中语句执行次数多,它花费时间就多。一个算法中的语句执行次数称为语句频度或时间频度。记为T(n)。
(2)时间复杂度在刚才提到的时间频度中,n称为问题的规模,当n不断变化时,时间频度T(n)也会不断变化。但有时我们想知道它变化时呈现什么规律。为此,我们引入时间复杂度概念。一般情况下,算法中基本操作重复执行的次数是问题规模n的某个函数,用T(n)表示,若有某个辅助函数f(n),使得当n趋近于无穷大时,T(n)/f(n)的极限&#20540;为不等于零的常数,则称f(n)是T(n)的同数量级函数。记作T(n)=O(f(n)),称O(f(n))
为算法的渐进时间复杂度,简称时间复杂度。
在各种不同算法中,若算法中语句执行次数为一个常数,则时间复杂度为O(1),另外,在时间频度不相同时,时间复杂度有可能相同,如T(n)=n2&#43;3n&#43;4与T(n)=4n2&#43;2n&#43;1它们的频度不同,但时间复杂度相同,都为O(n2)。按数量级递增排列,常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n),
线性对数阶O(nlog2n),平方阶O(n2),立方阶O(n3),...,
k次方阶O(nk),指数阶O(2n)。随着问题规模n的不断增大,上述时间复杂度不断增大,算法的执行效率越低。
2、空间复杂度与时间复杂度类&#20284;,空间复杂度是指算法在计算机内执行时所需存储空间的度量。记作: S(n)=O(f(n))
我们一般所讨论的是除正常占用内存开销外的辅助存储单元规模。讨论方法与时间复杂度类&#20284;,不再赘述。
(3)渐进时间复杂度评价算法时间性能  主要用算法时间复杂度的数量级(即算法的渐近时间复杂度)评价一个算法的时间性能。
2、类&#20284;于时间复杂度的讨论,一个算法的空

我要回帖

更多关于 初学吉他谱 的文章

 

随机推荐