1. . release版本的可执行程序为什么非常大?
程序一般分为Debug版本和Release版本,Debug版本用于内部调试,Release版本发行给用户使用
Release和Debug有什么不同
Release版称为发行版,Debug版称为调试版。
Debug中可以单步执行、跟踪等功能,但生成的可执行文件比较大,代码运行速度较慢。Release版运行速度较快,可执行文件较小,但在其编译条件下无法执行调试功能。
Release的exe文件链接的是标准的MFC DLL(Use MFC in a shared or static dll)。这些DLL在安装Windows的时候,已经配置,所以这些程序能够在没有安装Visual C++ 6.0的机器上运行。而Debug版本的exe链接了调试版本的MFC DLL文件,在没有安装Visual C++6.0的机器上不能运行,因为缺相应的DLL,除非选择use static dll when link。
sizeof(d)? 为什么在不同的平台上得到的值不一样?
C++拷贝构造函数和赋值运算符有那些不同和相同点。
拷贝构造函数和赋值号的异同
同:都可以对对象的成员进行赋值
异:
拷贝构造函数首先是一个构造函数,它调用的时候产生一个对象,是通过参数传进来的那个对象来初始化,产生的对象。
赋值是把一个对象赋值给一个原有的对象,而且还要检查一下两个对象是不是同一个对象,如果是的话就不做任何操作。
软件开发过程包含哪些阶段?各阶段质量保证措施是什么?
、1.需求分析
需求分析是开发人员对系统需要做什么和如何做的定义过程。从系统分析的经验来看,这个过程往往是个循序渐进的过程,一次性对系统形成完整的认识是困难的。只有不断地和客户领域专家进行交 流确认,方能逐步明了用户的需求。从系统开发的过程得知,系统分析时犯下的错误,会在接下来的阶段被成倍的放大,越是在开发的后期,纠正分析时犯下的错误所花费的代价越是昂贵,也越发影响系统的工期和系统的质量。
解决系统分析错误的方法我们公司通常采用邀请用户参与进行需求评定,然后对其用户的意见由质保成员跟踪检测是否纳入需求规格说明书,同时与用户签字确认形成需求基线,交由配置管理员放入配置管理库。
b、系统设计
优良的体系结构应当具备可扩展性和可配置性,而好的体系结构则需要好的设计方法,自然设计选型成为了系统设计首要的工作,究竟是采用哪种设计方法好呢?
对于设计选型不能一概而论,需要针对项目的结构、项目的特征和用户的需求来分析,同样也要考虑到参与项目小组成员的素质,如果其中大部分都没有从事过面向对象的设计且项目进对紧迫,这样没有多余的时间来培训小组成员来掌握面向对象的设计方法,尽管众所周知面向对象设计方法的优势,我们还是不如采用面向过程的方式(除用户指定开发设计方式外)可以减少项目承担的技术风险。
c、实现
实现也就是代码的生产过程。这里不仅包括代码的产生,同时也包括测试用例的产生。针对上一阶段提供详细设计,程序员开始编码并且调试程序,测试人员则根据设计进行测试用例的设计,设计出来的用例需要得到项目组成员认可由项目经理审核通过才能进入配置库。同时程序员调试完程序提交测试人员进行程序正确性检测。
d、文档管理
文档维护主要是配置管理小组的工作。文档从用途上分主要分为内部文档和外部文档。
内部文档包括: 项目开发计划; 需求分析; 体系结构设计说明; 详细设计说明; 构件索引; 构件成分说明; 构件接口及调用说明; 组件索引; 组件接口及调用说明; 类索引; 类属性及方法说明; 测试报告; 测试统计报告; 质量监督报告; 源代码; 文档分类版本索引; 软件安装打包文件。
外部文档主要包括: 软件安装手册; 软件操作手册; 在线帮助; 系统性能指标报告; 系统操作索引。
3、系统维护质量保证
2. 使用C++赋值运算符应注意什么地方?
如果函数的返回值是一个对象,有些场合用“引用传递”替换“值传递”可以提高效率。而有些场合只能用“值传递”而不能用“引用传递”,否则会出错。
对于赋值函数,应当用“引用传递”的方式返回String对象。如果用“值传递”的方式,虽然功能仍然正确,但由于return语句要把 *this拷贝到保存返回值的外部存储单元之中,增加了不必要的开销,降低了赋值函数的效率。
对于相加函数,应当用“值传递”的方式返回String对象。如果改用“引用传递”,那么函数返回值是一个指向局部对象temp的“引用”。由于temp在函数结束时被自动销毁,将导致返回的“引用”无效
3.exit()和_exit() 的区别。
exit()’与‘_exit()’的基本区别在于前一个调用实施与调用库里用户状态结构(user-mode constructs)有关的清除工作(clean-up),而且调用用户自定义的清除程序,在退出程序前,关闭文件,清除缓存。后一个函数只为进程实施内核清除工作。不关闭文件,不清楚缓存。
4.哪些方法可让一个进程仅有一个实例运行?
设置临界区,(可以定义自旋锁不?)使用Microsoft提供的互斥类Mutex
使用API函数,获取当前进程,遍历正在有相同名字运行的进程。
红黑树比AVL树的优势在哪?
红黑树引入了“颜色”的概念。引入“颜色”的目的在于使得红黑树的平衡条件得以简化。正如著名的密码学专家Bruce Schneier所说的那样
,“Being Partly balanced can be good enough”,红黑树并不追求“完全平衡”——它只要求部分地达到平衡要求,降低了对旋转的要求
,从而提高了性能。红黑树能够以O(log2 n)的时间复杂度进行搜索、插入、删除操作。此外,由于它的设计,任何不平衡都会在三次旋转之内
解决。当然,还有一些更好的,但实现起来更复杂的数据结构能够做到一步旋转之内达到平衡,但红黑树能够给我们一个比较“便宜”的解决
方案。红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高。
5.阻塞模式的recv在没受到数据的情况下如何返回?(不能将socket修改为非阻塞)
recv(fd,buf,sizeof(buf),flag)函数原型
将recv()函数的标志位设为停止等待 或者设置一个延时退出,超时返回什么的
recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
这里采用了MSG_DONTWAIT标志,它的作用是告诉recv()函数如果有数据到来的话就接受全部数据并立刻返回,没有数据的话也是立刻返回,而不进行任何的等待。这里的MSG_DONTWAIT 是我们自己定义的一个标志。
6.strcpy()为什么会造成缓冲区溢出?可用哪个函数替代?
造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数,strcpy()函数将源字符串复制到缓冲区。没有指定要复制字符的具体数目。复制字符的数目直接取决于源字符串中的数目。如果源字符串碰巧来自用户输入,且没有专门限制其大小,则有可能会导致缓冲区溢出。可用strncpy() 函数替代
7.哪些方法可以避免或减少锁的使用
8. 给定一个int型数n,写一个尽可能简单的函数判断n是否为2的幂,不能用循环。
答:bool foo(int n)
{
int a=n;
if(n<=0) return false;
a=((a&0xAAAAAAAA)>>1)+(a&0x55555555); a=((a&0xCCCCCCCC)>>2)+(a&0x33333333); a=((a&0xF0F0F0F0)>>4)+(a&0x0F0F0F0F); a=((a&0xFF00FF00)>>8)+(a&0x00FF00FF); a=((a&0xFFFF0000)>>16)+(a&0x0000FFFF);
if(a==1) return true;
else return false;
}
9.能识别正则表达式的命令有:grep egrep
10..VC中有哪些方法避免C编译头文件重复。
#ifndef #define #endif #pragma once
11.extern "C"的用法。
用于 提供 C 接口, 如使用 C 命名方式 等
作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的
.
12.异步socket编程中,send不出数据的错误码是什么,(举Linux或Windows为例),你是怎么处理的?
非阻塞SOCKET,SEND不出数据的原因,TCP下连接断开了和该SOCKET处在阻塞状态(也就是说在发送数据中)。UPD发不出可能是SOCKET处于阻塞状态。
处理的办法就是记录下该SOCKET的状态,当状态为阻塞的时间,放入缓冲,当该SOCKET再次可写时,发送。
13..函数前的static和volatile变量中关键字的作用
(1)auto
这个这个关键字用于声明变量的生存期为自动,即将不在任何类、结构、枚举、联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量。这个关键字不怎么多写,因为所有的变量默认就是auto的。
(2)register
这个关键字命令编译器尽可能的将变量存在CPU内部寄存器中而不是通过内存寻址访问以提高效率。
(3)static
常见的两种用途:
1>统计函数被调用的次数;
2>减少局部数组建立和赋值的开销.变量的建立和赋值是需要一定的处理器开销的,特别是数组等含有较多元素的存储类型。在一些含有较多的变量并且被经常调用的函数中,可以将一些数组声明为static类型,以减少建立或者初始化这些变量的开销.
详细说明:
1>、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
2>、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。
3>当static用来修饰全局变量时,它就改变了全局变量的作用域,使其不能被别的程序extern,限制在了当前文件里,但是没有改变其存放位置,还是在全局静态储存区。
(4)const
被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。它可以修饰函数的参数、返回值,甚至函数的定义体。
作用:
1>修饰输入参数
a.对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。
b.对于内部数据类型的输入参数,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。
2>用const修饰函数的返回值
a.如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。
如对于: const char * GetString(void);
如下语句将出现编译错误:
char *str = GetString();//cannot convert from 'const char *' to 'char *';
正确的用法是:
const char *str = GetString();
b.如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const修饰没有任何价值。 如不要把函数int GetInt(void) 写成const int GetInt(void)。
3>const成员函数的声明中,const关键字只能放在函数声明的尾部,表示该类成员不修改对象.
说明:
const type m; //修饰m为不可改变
示例:
typedef char * pStr; //新的类型pStr;
char string[4] = "abc";
const char *p1 = string;
p1++; //正确,上边修饰的是*p1,p1可变
const pStr p2 = string;
p2++; //错误,上边修饰的是p2,p2不可变,*p2可变
同理,const修饰指针时用此原则判断就不会混淆了。
const int *value; //*value不可变,value可变
int* const value; //value不可变,*value可变
const (int *) value; //(int *)是一种type,value不可变,*value可变
//逻辑上这样理解,编译不能通过,需要tydef int* NewType;
const int* const value;//*value,value都不可变
(5)volatile
表明某个变量的值可能在外部被改变,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。它可以适用于基础类型如:int,char,long......也适用于C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,结构或者类的所有成员都会被视为volatile.
14.7、异步IO和同步IO有什么区别?举例说明有几种(如read)?
异步IO当函数返回时不一定就完成了IO操作,而同步IO已经完成了。所以异步IO需要有一个事件,当IO完成时会设置此事件,调用者在事件上等待。一般说来,异步I/O是和同步I/O相比较来说的,如果是同步I/O,当一个I/O操作执行时,应用程序必须等待,直到此I/O执行完. 相反,异步I/O操作在后台运行,I/O操作和应用程序可以同时运行,提高了系统性能; 使用异步I/O会提高I/O流量,如果应用是对裸设备进行操作,这种优势更加明显
, 因此象数据库,文件服务器等应用往往会利用异步I/O,使得多个I/O操作同时执行.
8、32位系统中,出现结构字节对齐的问题和大小端的问题的避免?
指定对齐值:#pragma pack (value)时指定的对齐value
写一个函数判断系统是大端还是小端。若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1.
大端格式:在这种格式中,字数据的高字节存储在低地址中,而字数据的低字节则存放在高地址中
小端格式:与大端存储格式相反,在小端存储格式中,低地址中存放的是字数据的低字节,高地址存放的是字数据的高字节
联合体union的存放顺序是所有成员都从低地址开始存放。
Int checkCPU ()
{
Union w
{
Int a;
Char b;
}c;
c.a=1;
return (c.b==1);
}
14 如何在Release版本中查找以下问题,
a 内存泄漏 b 段错误导致非法操作 c 程序CPU占用100%
9、如何查出内存泄漏和非法操作的BUG(在Release版本下)?
检查window (release)下的内存泄漏
1、 放置关键字 assert()
2、 生成map 文件。它并不往可执行文件exe 中添加任何东西,只是在编译的时候将各个函数入口地址记录在后缀为.map的文件中,程序崩溃的时候可以得到一个EIP地址,通过地址知道崩溃所在函数
3、 可以设置断点,在希望设置断点的地方加入 _ASM int 3
4、 可以通过编译时的汇编程序看出
5、 采用第三方工具
查找段错误导致的非法操作用
ptrace系统调用跟踪调试运行中的进程(truss、strace或ltrace的原理都是根据ptrace系统调用跟踪调试运行中的进程)
用truss跟踪clint的系统调用来找出错误,clint是c++静态源码分析工具。通过ports安装好之后利用调试工具truss即可。
核心太与用户太的区别,x86如何转换。中断调用,从ring3转到ring0
现代的操作系统一般都有核心模式和用户模式之分,操作系统核心代码运行在核心模式下,具有较高的运行级别,具有较高的运行效率和较强的底层控制权力,系统硬件为其提供了尽可能多的内存保护以及其他措施,而用户进程则一般运行在用户模式下,其运行级别较低。
在x86平台下,核心态和用户态的区分主要是通过段选择子确定的,具体来说,在Linux环境下,核心态的代码段选择子为0x10,数据段选择子为0x18;而用户态的代码段选择子为0x23,数据段选择子为0x2B,因此核心态程序工作在ring0,而用户态程序工作在ring3。在用户模式下只能访问用户空间而在核心模式下可以访问系统空间和用户空间
从用户态进入核心态的最常用的方法是在寄存器eax填一个功能码,然后执行int 2e。这有点像DOS时代的DOS和BIOS系统调用。在NT架构中这种机制被称作system service。
Unix的启动顺序排序。
第一步:通过/boot/vm进行启动 vmlinuz
第二步:init /etc/inittab
第三步:启动相应的脚本,并且打开终端
rc.sysinit
rc.d(里面的脚本)
rc.local
第四步:启动login登录界面 login
第五步:在用户登录的时候执行sh脚本的顺序:每次登录的时候都会完全执行的
/etc/profile.d/file
/etc/profile
/etc/bashrc
/root/.bashrc
/root/.bash_profile
对比平衡二叉树AVL和红黑树。哈希表
平衡二叉树和哈希表,哈希表的查找比较快,而且时间复杂度为O(1),但是它所占的空间比较大,而且插入和删除操作不够灵活,没有动态性。
红黑树算法的时间复杂度和AVL相同,但统计性能比AVL树更高
由于红黑树也是二叉查找树,它们当中每一个节点的比较值都必须大于或等于在它的左子树中的所有节点,并且小于或等于在它的右子树中的所有节点。这确保红黑树运作时能够快速的在树中查找给定的值。
问答
const 有什么用途?(请至少说明两种)
Const用了定义一个常量,
1、 用在变量前面的时候可以避免变量被修改
2、 用在函数声明部分允许const 的类对象成员访问const 成员函数,如果类的成员函数不会对数据成员进行修改的话最好把该函数定义为const类型,这样无论是const的类对象还是非const 的类对象都可以访问该函数
3、 可以用来代替define ,define 只是简单的代替,但是const 还会进行类型检查。
怎么避免头文件重复包含
#ifndef H_HEADFILENAME
#define H_HEADFILENAME
文件内容….
#endif
#paragma once
C++拷贝构造函数和赋值运算符有那些不同和相同点。
同:都可以对对象的成员进行赋值
异:
拷贝构造函数首先是一个构造函数,它调用的时候产生一个对象,是通过参数传进来的那个对象来初始化,产生的对象。
赋值是把一个对象赋值给一个原有的对象,而且还要检查一下两个对象是不是同一个对象,如果是的话就不做任何操作。
Spinlock(自旋锁),mutex,(互斥)semaphore(信号量),vitical section(critical section)的作用与区别?
都是操作系统中内核同步实现数据访问,资源共享的作用
自旋锁是在cpu之间,只能运行一个对象,自旋锁可以在任何时刻防止多于一个的内核任务(进程)同时进入临界区,因此这种锁可有效地避免多处理器上并发运行的内核任务竞争共享资源。
互斥只能运行一个对象,可以使一个进程下的线程间同步。也可以是不同进程间同步,互斥能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享
信号量可以运行多个对象,但是为线程间同步它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目
临界区同一个进程下的线程间同步,只能运行一个对象。保证在某一个时间只有一个线程可以访问数据的方法
事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作
正则表达式字符串匹配问题。
比较哈希表和平衡二叉树的特点,它们分别如用那些场合。
哈希表和平衡二叉树的适用场合
哈希表:哈希表查找速度比较快,可以时间复杂度为O(1)的情况下找到实现查找。但是要耗比较多的内存。所以比较适用于对查找速度要求比较高、且内存空间足够的时候
平衡二叉树:如果想在以后用二分法查找的时候查找速度比较快的话用建立平衡二叉树的方法()平衡二叉树支持动态的插入和查找,保证操作在O(height)时间,这就是完成了哈希表不便完成的工作,动态性。平衡二叉树/红黑树就是为了将查找的时间复杂度保证在O(logN)范围内。所以如果输入结合确定,所需要的仅仅是查询,则可以考虑使用哈希表,如果输入集合不确定,则考虑使用平衡二叉树/红黑树,保证达到最大效率。
recv函数如何在阻塞模式下没有收到数据就返回
Rev(buff,sizeof(buff),flag)
可以将rev的标志位定义为无需等待返回,不管其是否收到数据都立即返回,也可以定义为等待时间,如果超过等待时间还没有接受到数据就立即返回,
用锁效率低,有那些方法可以避免或减少锁的使用?
将表建立表级锁,减少锁数量的使用
Main函数中两个参数的作用
第一个形参argc是一个整型变量,第二个形参argv是一个指针数组,其元素指向字符型
数据。用带参数的main函数可以直接从命令行得到参数值(这些值是字符串),在程序运行
时,可以根据输入的命令行中的不同情况进行相应的处理。利用main函数中的参数可以使
程序从系统得到所需的数据,增加了处理问题的灵活性。
进程的几个基本状态:就绪、执行、阻塞
1. 匹配"[10]:dddddd"和"[9]:abcdegf"但不匹配"[a]:xfdf"的正则表达式。\b\w{11}\b精确匹配只有11个字符的字符串
5 c++中虚函数如何定义,使用时应该注意什么?
虚函数是在类中被声明为virtual的成员函数,虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。
多态指同一个方法根据其所属的不同对象可以有不同的行为(根据自己理解,不知这么说是否严谨)。
8. 有红、绿、蓝三色球分别3,2,1个。取任意两个不同颜色的球都会使它们变成第三种颜色的两个球。问最少取多少次,可使这些球都变成同一种颜色?
答:无论多少次,都不可以使这些球变成同一种颜色,
分析:
一、对于(R,R,R,G,G,B)即(3,2,1),有:
i. (R,G) ---> (B,B,B,R,R,G)即(3,2,1)
ii. (R,B) ---> (G,G,G,G,R,R)即(4,2)
iii. (G,B) ---> (R,R,R,R,R,G)即(5,1)
对于(G,G,G,G,R,R)即(4,2),有 :
i. (R,G) ---> (G,G,G,B,B,R)即(3,2,1)
对于(R,R,R,R,R,G)即(5,1)有 :
i. (R,G) ---> (R,R,R,R,B,B)即(4,2)
因此,只有三种状态(3,2,1), (4,2)和(5,1),不可能出现(6,0)这种情况。
6用C/C++编程,从1到100中取出10个不同的数,要求打印出所有可能的组合;
#include <iostream.h>
int source[100];
int dest[10]={0};
int index_source=0;
int index_dest=0;
int k=10;
int i=0;
int j=0;
void composition(int source[], int index_source, int dest[], int index_dest, int k)
{
if(100-index_source==k)
{
for(i=0; i<index_dest; i++)
{
cout<<dest<<" ";
}
for(i=index_source; i<100; i++)
{
cout<<source<<" ";
}
cout<<endl;
return;
}
if(index_source<100 && k==1)
{
for(i=index_source; i<100; i++)
{
for(j=0; j<index_dest; j++)
{
cout<<dest[j]<<" ";
}
cout<<source<<endl;
}
return;
}
composition(source, index_source+1, dest, index_dest, k);
dest[index_dest++]=source[index_source];
composition(source, index_source+1, dest, index_dest, k-1);
}
void main()
{
for(int i=0;i<100;i++)
source=i;
composition(source,0, dest, 0, 10);
}
8 如何判断两个单向链表是否有相交,并找出交点。
给出两个链表的头指针pHead1 和 pHead2 ,写一个函数判断两条链表有没交叉点
Node* checkLink(Node* pHead1,Node* pHead2)
{
Node* p1=pHead1,p2=pHead2;
int i=1,j=1;
if(p1==NULL || p2==NULL)
return NULL;
if(p1==p2)
return p1;
while(p1->pNext!=NULL)
{
p1=p1->pNext;
i++;
}
while(p2->pNext!=NULL)
{
p2=p2->pNext;
j++;
}
if(p1==p2)
return NULL;
else
{
for(int k=0;k<fabs(a-b);k++)
{
if(i>j)
p1=p1->pNext;
else
p2=p2->pNext;
}
while(p1!=p2)
{
p1=p1->pNext;
p2=p2->pNext;
}
return p1;
}
}
有1001个球,两个人轮流拿球,且每次只能拿1、2、4个球,规定拿到最后一个球的人为输。如果让你先拿,你是否有必胜的把握。如果有,该如何操作?
解:先拿4个,然后每轮保证两个人所拿球数之和为3或6。
【编程】用最简单的函数实现功能:判断一个int数据是否是2的x次幂(不能使用循环)。
bool Juge(int dat, int x)
{
return !(dat & ~(1 << x));
}
【编程】对于一个给定的整形数组int array[n]。编程实现:将数组中所有小于或等于0的元素都放在数组前面,大于0的元素放在数组后面。要求时间复杂度为o(n)
void Divide(int array[], int n)
{
int i = 0;
for (int j = 0; j < n; j++)
{
if (array[j] < 0)
{
int temp;
temp = array[i];
array[i] = array[j];
array[j] = temp;
i++;
}
}
}
【简答】用一个程序示意常见的错误能够导致栈破坏,如何检查?
#include "iostream.h"
#include "string.h"
void main()
{
char str[5];
cout<<"input: ";
cin>>str;
while(strlen(str)>5)
{
cout<<"too long!"<<endl;
cin>>str;
}
cout<<str<<endl;
}
如果系统堆栈很小,不能处理超过4级的函数调用,如何解决八皇后问题
:#include <iostream>
#include <math.h>
#include <malloc.h>
using namespace std;
int *position; //放置的位置
int queen; //皇后数目
int count; //第N种可能性//判断第n行是否放置皇后
bool SignPoint(int n)
{
for (int i=0;i<n;i++)
{
if (*(position+i) == *(position+n)) //该列已经放置过皇后了
return false;
if (abs(*(position+i) - *(position+n)) == n-i) //对角线已经放置过了
return false;
}
return true;
}//设置皇后
void SetQueen(int n=0)
{
if (queen==n)
{
//该处可以改成自己想要的显示方式
printf("NO.%d: ",++count);
printf("\n");
for (int i=0;i<queen;i++)
{
for (int j=0;j<queen;j++)
{
if (j == position[i])
{
printf("* ");
}
else
{
printf("0 ");
}
}
printf("\n");
}
printf("\n");
return;
}
else
{
for (int i=0;i<queen;i++)
{
position[n] = i; if(SignPoint(n))//如果该位置放置皇后正确的话,则到下一行
{
SetQueen(n+1);
}
}
}
}int _tmain(int argc, _TCHAR* argv[])
{
cout<<"请输入皇后的总数:"<<endl;
cin>>queen;
position = (int*)malloc(sizeof(int));
SetQueen();
cout<<"摆放完毕"<<endl;
cin.get();
cin.get();
return 0;
}
% & . && <= = 那个优先级别最高
. & % <= && =
【知识点】数据类型占字节数(32位机)
Sizeof(long)=4; (*)
Sizeof(int) = 4;
Sizeof(short)=2;
Sizeof(char)=1;
Sizeof(double)=8;
Sizeof(float)=4;
(*)对于类(包括结构体),其大小为size=Max{sizeof(除静态类型的成员变量)}的整数倍。且满足size>=sum{sizeof(除静态类型的成员变量)}
【填空】下面的程序在那行崩溃:
struct {
char c;
char *pc;
} a;
int main(int argc, char* argv[])
{
char *p=&a.c;
p[0]=0;
p[1]=0;
p[2]=0;
p[3]=0;
p[4]=0;
p[5]=0;
a.pc=p;
a.pc[5]=0; *
a.pc[4]=0;
a.pc[3]=0;
a.pc[2]=0;
a.pc[1]=0;
a.pc[0]=0;
return 0;
}
【编程】实现函数atoi和itoa。
int atoi(const char *s)
{
int dat = 0;
bool neg = false;
if (*s == '-')
{
s++;
neg = true;
}
while(*s)
{
dat *= 10;
if (*s >= '0' || *s <= '9')
dat += *s - '0';
else
return -1;
s++;
}
return neg ? (-1*dat) : dat;
}
// num:待转换的整型变量 str:指向存放结果 radix:基数
char *itoa(int num, char *str, int radix)
{
Const char table[]
= "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char *ptr = str;
if (num == 0)
{ //数值为0时
*ptr++ = '0';
*ptr = '\0';
return str;
}
bool neg = false;
if (num < 0)
{ //数值小于0时,添加负号,并将指针后移
neg = true;
num *= -1;
*ptr++ = '-';
}
while (num)
{
*ptr++ = table[num % radix];
num /= radix;
}
*ptr = '\0';
//反转字符串
char *beg = (neg ? str +1 : str);
ptr--;
while (beg < ptr)
{
int temp = *beg;
*beg = *ptr;
*ptr = temp;
beg++;
ptr--;
}
return str;
}
【知识点】reinterpret_cast
reinterpret_cast是C++里的强制类型转换符。
操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。
例如:int *n= new int ;
double *d=reinterpret_cast<double*> (n);
在进行计算以后, d 包含无用值. 这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析。
因此, 需要谨慎使用 reinterpret_cast.
并且:reinterpret_cast 只能在指针之间转换。
== ===========================================
== static_cast .vs. reinterpret_cast
== ================================================
reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。(这句话是C++编程思想中的原话)
static_cast 和 reinterpret_cast 操作符修改了操作数类型。它们不是互逆的; static_cast 在编译时使用类型信息执行转换,在转换执行必要的检测(诸如指针越界计算, 类型检查). 其操作数相对是安全的。另一方面;reinterpret_cast 仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换, 例子如下:
int n=9; double d=static_cast < double > (n);
上面的例子中, 我们将一个变量从 int 转换到 double。 这些类型的二进制表达式是不同的。 要将整数 9 转换到 双精度整数 9,static_cast 需要正确地为双精度整数 d 补足比特位。其结果为 9.0。而reinterpret_cast 的行为却不同:
int n=9;
double d=reinterpret_cast<double & > (n);
这次, 结果有所不同. 在进行计算以后, d 包含无用值. 这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析.
因此, 你需要谨慎使用 reinterpret_cast.
【知识点】dynamic_cast
用法:dynamic_cast < type-id > ( expression )
该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;
如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class B{
public:
int m_iNum;
virtual void foo();
};
class D:public B{
public:
char *m_szName[100];
};
void func(B *pb){
D *pd1 = static_cast<D *>(pb);
D *pd2 = dynamic_cast<D *>(pb);
}
在上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;
但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),
而pd2将是一个空指针。
另外要注意:B要有虚函数,否则会编译出错;static_cast则没有这个限制。
这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表(
关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,
没有定义虚函数的类是没有虚函数表的。
另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。
class A{
public:
int m_iNum;
virtual void f(){}
};
class B:public A{
};
class D:public A{
};
void foo(){
B *pb = new B;
pb->m_iNum = 100;
D *pd1 = static_cast<D *>(pb); //compile error
D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL
delete pb;
}
在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。
【知识点】const_cast
用法:const_cast<type_id> (expression)
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
常量指针被转化成非常量指针,并且仍然指向原来的对象;
常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
Voiatile和const类试。举如下一例:
class B{
public:
int m_iNum;
}
void foo(){
const B b1;
b1.m_iNum = 100; //comile error
B b2 = const_cast<B>(b1);
b2. m_iNum = 200; //fine
}
上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;
使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。
【知识点】static_cast
用法:static_cast < type-id > ( expression )
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类和子类之间指针或引用的转换。
进行上行转换(把子类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。
C++中static_cast和reinterpret_cast的区别
C++primer第五章里写了编译器隐式执行任何类型转换都可由static_cast显示完成;reinterpret_cast通常为操作数的位模式提供较低层的重新解释
1、C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作。因此,被做为隐式类型转换使用。比如:
int i;
float f = 166.7f;
i = static_cast<int>(f);
此时结果,i的值为166。
2、C++中的reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释。比如:
int i;
char *p = "This is a example.";
i = reinterpret_cast<int>(p);
此时结果,i与p的值是完全相同的。reinterpret_cast的作用是说将指针p的值以二进制(位模式)的方式被解释为整型,并赋给i,一个明显的现象是在转换前后没有数位损失。
【简答】release版本的可执行文件。如何获知生成的文件是否过大。
1是数制转换151转2进制和九进制。10010111 177
2已知0的ASCII码为0x40,那么int 120;在内存中的表示形式是0x__ 78(0的ASCII码为0x40,应该为0x30)
3
1、在linux下,查看目录大小的命令是:du $ du -sm
2、修改文件属性的命令是:chmod [options] mode files
3、切换为其他用户身份的命令是:su-filemane
4还有一道指针交换数值 int i=0,j=10,int* p=&i, int* q=&j,
int fun (**a,*b)
{int* temp=a;
*a*=10;
*b*=10;
a=b;
b=temp;
}最后问调用fun(&p,q)问i、j、p、q的最终值(具体形式大概如此,但中间指针肯定记的错误)
此题主要考察指针指向一个整数,然后利用指针改变变量,最后交换指针
· 5有道填插入排序的算法。有一个数组a[0] 到 a[i-1]为从小到大排序,a[i] 到a[count-1]没有排序,请您添加3条语句使它们按照从小到大排序
int insert_sort(int a[],int count)
{
for(int i=1;i<count;++i)
{
int j,t;
t=a[i];
(j=i-1;)
while(j>=0&&t<a[j])
{
(a[j+1]=a[j];)
j--;
}
(a[j+1]=t;)
}
return 0;
}
三,编程与逻辑题
1自己写一个strstr
(单链表判断有无环,)
char* strstr(char* buf, char* sub)
{
char* bp;
char* sp;
If(!*sub)
return buf;
while(*buf)
{bf=buf;
sp=sub;
do{ if(!*sp)
return buf;
}
while(*bp++==*sp++)
buf+=1;
}
return 0;
}
2遍历文本找单词并删掉出现频率最少的单词,fun (char* pText)
#include <stdio.h>
#include <stdarg.h> //定义av_list、av_start、av_arg等宏
· 3实现一个与printf功能相似的函数
#include <iostream>
#include <conio.h>
#include <stdio.h>
#include <stdarg.h> //定义av_list、av_start、av_arg等宏
/*******************************************************************
此函数的作用:
实现一个参数个数可变的函数,此函数的功能与printf类似,
但在格式处理上,不如printf丰富
无异常,返回一个true,否则返回false
format字符串的合法情况如下:
1."%%zyk%%zyk%%",OUTPUT:%zyk%zyk%
2."%dzyk%fzyk%s",OUTPUT:(int)zyk(float)zyk(string)
3."zyk", OUTPUT:zyk
非法情况如下:
1."%zyk%" ERROR:不存在%z格式、%后面必须跟一个格式字符
*******************************************************************/
bool zykPrintf(const char * format,...)
{
//定义一个可用于指向参数的指针(实为char *),
va_list argPtr;
//把函数的第一个参数format的地址传给argPtr
va_start(argPtr,format);
const int size = strlen(format)+1;
char *tmp = new char[size];
memset(tmp, 0, size);
· while (*format != 0)
{
int i;
for (i=0; i<size && *format!='%' && *format!=0; i++)
{
tmp[i]=*format++;
}
tmp[i] = 0; //在有效的字符串末尾作0值防护
printf("%s",tmp);
if (*format == 0)
return true;
switch(*++format)
{
//按指定类型读取下一个参数,并打印
case 'd': { printf("%d", va_arg(argPtr, int)); break;}
case 's': { printf("%s", va_arg(argPtr, char *)); break; }
case 'c': { printf("%c", va_arg(argPtr, char)); break;}
case 'f': { printf("%f", va_arg(argPtr, float)); break;}
//对%%的处理
case '%': { printf("%%"); break; }
//格式错误
default : { printf(" Error Ocurr!Please Check the Format!"); return false;}
}
++format;
}
delete[] tmp;
return true;
}
int main(int argc,char * argv[])
{
zykPrintf("%zyk"); //error
zykPrintf("zyk%"); //error
zykPrintf("%%zyk%%zyk%%"); //OUTPUT: %zyk%zyk%
zykPrintf("\nzyk is a pretty boy! His age is %d and %s",5,"I love zyk^_^!");
getch();
return 0;
}
1、strstr的实现原型。
char *my_strstr(const char *str, const char *strSearch)
{
while (*str != '\0')
{
char *p = (char *)str;
char *ps = (char *)strSearch;
while ( ps && *p == *ps )
p , ps ;
if ('\0' == *ps)
return (char *)str;
str ;
}
return NULL;
}
3、printf的实现。
int printf(const char *format, ...)
{
va_list arglist;
int buffing;
int retval;
va_start(arglist, format);
_ASSERTE(format != NULL);
#ifdef _MT
_lock_str2(1, stdout);
__try {
#endif /* _MT */
buffing = _stbuf(stdout);
retval = _output(stdout,format,arglist);
_ftbuf(buffing, stdout);
#ifdef _MT
}
__finally {
_unlock_str2(1, stdout);
}
#endif /* _MT */
return(retval);
}
8 如何判断两个单向链表是否有相交,并找出交点。
给出两个链表的头指针pHead1 和 pHead2 ,写一个函数判断两条链表有没交叉点
Node* checkLink(Node* pHead1,Node* pHead2)
{
Node* p1=pHead1,p2=pHead2;
int i=1,j=1;
if(p1==NULL || p2==NULL)
return NULL;
if(p1==p2)
return p1;
while(p1->pNext!=NULL)
{
p1=p1->pNext;
i++;
}
while(p2->pNext!=NULL)
{
p2=p2->pNext;
j++;
}
if(p1==p2)
return NULL;
else
{
for(int k=0;k<fabs(a-b);k++)
{
if(i>j)
p1=p1->pNext;
else
p2=p2->pNext;
}
while(p1!=p2)
{
p1=p1->pNext;
p2=p2->pNext;
}
return p1;
}
}
17.面向对象的三个基本特征,并简单叙述之?
1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)
2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。
3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
18. 重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?
常考的题目。从定义上来说:
重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
重写:是指子类重新定义复类虚函数的方法。
从实现原理上来说:
重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
19. 多态的作用?
主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。
20. Ado与Ado.net的相同与不同?
除了“能够让应用程序处理存储于DBMS 中的数据“这一基本相似点外,两者没有太多共同之处。但是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+x ,i = 5*DOUBLE(5); i 是多少?i=5*5+5
答案:i 为30。
23. 有哪几种情况只能用intialization list 而不能用assignment?
答案:当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。
24. C++是不是类型安全的?
答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
25. main 函数执行以前,还会执行什么代码?
答案:全局对象的构造函数会在main 函数之前执行。
26. 描述内存分配方式以及它们的区别?
1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
27.struct 和 class 的区别
答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和 class 在其他方面是功能相当的。
从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结构仅仅象一堆缺乏封装和功能的开放的内存位,而类就象活的并且可靠的社会成员,它有智能服务,有牢固的封装屏障和一个良好定义的接口。既然大多数人都这么认为,那么只有在你的类有很少的方法并且有公有数据(这种事情在良好设计的系统中是存在的!)时,你也许应该使用 struct 关键字,否则,你应该使用 class 关键字。
28.当一个类A 中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,1
29.如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk)
答案:肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。
29. 在8086 汇编下,逻辑地址和物理地址是怎样转换的?(Intel)
答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器内地址,就得到了真正要访问的地址。
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++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 字节
cout<< sizeof(p) << endl; // 4 字节
计算数组和指针的内存容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 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 + b ) + 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" );
}
int fn1()
{
printf( "next.\n" );
return 0;
}
int fn2()
{
printf( "executed " );
return 0;
}
int fn3()
{
printf( "is " );
return 0;
}
int fn4()
{
printf( "This " );
return 0;
}
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++编译程序编译的?
答案:
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
39.文件中有一组整数,要求排序后输出到另一个文件中
答案:
#i nclude<iostream>
#i nclude<fstream>
using namespace std;
void Order(vector<int>& data) //bubble sort
{
int count = data.size() ;
int tag = false ; // 设置是否需要继续冒泡的标志位
for ( int i = 0 ; i < count ; i++)
{
for ( int j = 0 ; j < count - i - 1 ; j++)
{
if ( data[j] > data[j+1])
{
tag = true ;
int temp = data[j] ;
data[j] = data[j+1] ;
data[j+1] = temp ;
}
}
if ( !tag )
break ;
}
}
void main( void )
{
vector<int>data;
ifstream in("c:\\data.txt");
if ( !in)
{
cout<<"file error!";
exit(1);
}
int temp;
while (!in.eof())
{
in>>temp;
data.push_back(temp);
}
in.close(); //关闭输入文件流
Order(data);
ofstream out("c:\\result.txt");
if ( !out)
{
cout<<"file error!";
exit(1);
}
for ( i = 0 ; i < data.size() ; i++)
out<<data[i]<<" ";
out.close(); //关闭输出文件流
}
40. 链表题:一个链表的结点结构
struct Node
{
int data ;
Node *next ;
};
typedef struct Node Node ;
(1)已知链表的头结点head,写一个函数把这个链表逆序 ( Intel)
Node * ReverseList(Node *head) //链表逆序
{
if ( head == NULL || head->next == NULL )
return head;
Node *p1 = head ;
Node *p2 = p1->next ;
Node *p3 = p2->next ;
p1->next = NULL ;
while ( p3 != NULL )
{
p2->next = p1 ;
p1 = p2 ;
p2 = p3 ;
p3 = p3->next ;
}
p2->next = p1 ;
head = p2 ;
return head ;
}
(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->next;
p2 = head2 ;
}
else
{
head = head2 ;
p2 = head2->next ;
p1 = head1 ;
}
Node *pcurrent = head ;
while ( p1 != NULL && p2 != NULL)
{
if ( p1->data <= p2->data )
{
pcurrent->next = p1 ;
pcurrent = p1 ;
p1 = p1->next ;
}
else
{
pcurrent->next = p2 ;
pcurrent = p2 ;
p2 = p2->next ;
}
}
if ( p1 != NULL )
pcurrent->next = p1 ;
if ( p2 != NULL )
pcurrent->next = p2 ;
return head ;
}
(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);
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2->next);
}
return head ;
}
41. 分析一下这段程序的输出 (Autodesk)
class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i) //B(int) works as a converter ( int -> instance of B)
{
cout<<"constructed by parameter " << data <<endl;
}
private:
int data;
};
B Play( B b)
{
return 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 < count ; i++)
{
if ( data[i] > maxnumber )
{
sec_max = maxnumber ;
maxnumber = data[i] ;
}
else
{
if ( data[i] > sec_max )
sec_max = data[i] ;
}
}
return sec_max ;
}
43. 写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。
KMP算法效率最好,时间复杂度是O(n+m)。
44. 多重继承的内存分配问题:
比如有class A : public class B, public class C {}
那么A的内存结构大致是怎么样的?
这个是compiler-dependent的, 不同的实现其细节可能不同。
如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。
可以参考《深入探索C++对象模型》,或者:
http://blog.youkuaiyun.com/wfwd/archive/2006/05/30/763797.aspx
45. 如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针)
struct node { char val; node* next;}
bool check(const node* head) {} //return false : 无环;true: 有环
一种O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然):
bool check(const node* head)
{
if(head==NULL) return false;
node *low=head, *fast=head->next;
while(fast!=NULL && fast->next!=NULL)
{
low=low->next;
fast=fast->next->next;
if(low==fast) return true;
}
return false;
}