1. char a[5],*p=a;以下说法正确的是 A
A p=”abcd” Ba=”abcd”; C *p=”abcd”; D *a=”abcd”
分析:p里面存的是数组a的首元素的地址,p是一个指针变量,指向字符型数据,指向字符变量,不是把“abcd”这些字符存放到p中,也不是把字符串赋给*p,只是把“abcd”的第一个字符的地址赋给指针变量p。
2. 指针变量的值是指指针所指向的变量在内存中的地址。(正确)
3. 下面代码输出结果是
int main() {
intpid;
intnum=1;
pid=fork();
if(pid>0){
num++;
printf("inparent:num:%d addr:%x\n",num,&num);
}
elseif(pid==0) {
printf("inchild:num:%d addr:%x\n",num,&num);
}
}
B、父子进程中输出的num不同,num地址相同。
分析:虚拟地址空间。num地址的值相同,但是其真实的物理地址却不一样。 linux下实现了一下,发现地址值真的一样。 fork之后子进程复制了父进程的数据、堆栈。但是由于地址重定位器之类的魔法存在, 所以,看似一样的地址空间(虚拟地址空间), 其实却是不同的物理地址空间。 同时可以验证c程序中输出的地址空间其实都是虚拟地址空间。
分析二:
fork()之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但是在操作系统看来,他们更像兄弟关系,这2个进程共享代码空间,但是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝,指令指针也完全相同,但只有一点不同,如果fork成功,子进程中fork的返回值是0,父进程中fork的返回值是子进程的进程号,如果fork不成功,父进程会返回错误。可以这样想象,2个进程一直同时运行,而且步调一致,在fork之后,他们分别作不同的工作,也就是分岔了。这也是fork为什么叫fork的原因。
勘误:子进程的pid是0,子进程的getpid()是它自己的进程号;父进程中的pid值为子进程进程号,只有父进程执行的getpid()才是他自己的进程号。
4. 下面说法正确的是(A)
A 一个空类默认一定生成构造函数,拷贝构造函数,赋值操作符,引用操作符,析构函数。
B可以有多个析构函数
C 析构函数可以为virtual,可以被重载
D 类的构造函数如果都不是public访问属性,则类的实例无法创建
分析:C析构函数没有参数列表,无法重载,但可以重写
D单列模式下,成员函数私有可以实例化。
单例模式也称为单件模式、单子模式,可能是使用最广泛的设计模式。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
单例模式有许多种实现方法,在C++中,甚至可以直接用一个全局变量做到这一点,但这样的代码显的很不优雅。使用全局对象能够保证方便地访问实例,但是不能保证只声明一个对象——也就是说除了一个全局实例外,仍然能创建相同类的本地实例。
《设计模式》一书中给出了一种很不错的实现,定义一个单例类,使用类的私有静态指针变量指向类的唯一实例,并用一个公有的静态方法获取该实例。
单例模式通过类本身来管理其唯一实例,这种特性提供了解决问题的方法。唯一的实例是类的一个普通对象,但设计这个类时,让它只能创建一个实例并提供对此实例的全局访问。唯一实例类Singleton在静态成员函数中隐藏创建实例的操作。习惯上把这个成员函数叫做Instance(),它的返回值是唯一实例的指针。
用户访问唯一实例的方法只有GetInstance()成员函数。如果不通过这个函数,任何创建实例的尝试都将失败,因为类的构造函数是私有的。GetInstance()使用 懒惰初始化,也就是说它的返回值是当这个函数首次被访问时被创建的 。这是一种防弹设计——所有GetInstance()之后的调用都返回相同实例的指针。
5. 分析下述代码
#include<iostream>
using namespace std;
int main() {
inta[2][5]={{2,3,4,5,6},{7,8,9,10,11}};
int*ptr=(int *)(&a+1);
cout<<*(ptr-3)<<endl;
return0;
}
分析:数组名是数组这种类型的变量名,所以对数组名取地址是取的整个数组的地址,所以&a+1自然要跨过整个数组的长度,本题即跨过2*5 = 10个int的长度。
有了上述概念,再来分析这道题,int *ptr = (int*)(&a+1),p此时指向的地址应该是a[1][4]后面的地址,由于ptr是int型指针,ptr-3应该是ptr向前移动3个元素,即ptr-3指向a[1][2],所以*(ptr -3) = 9
#include<iostream>
using namespace std;
int main() {
inta[][5]={{2,3,4,5,6},{7,8,9,10,11},{12,13,14,15,16}};
int*p=(int *)(&a+1);
int*p2=(int *)(a+1);
int*p3=(int *)(a[0]+1);
cout<<*(p-3)<<""<<*p2<<" "<<*p3<<endl;
return0;
}
输出 14 7 3
分析:a是数组首地址,也就是a[0]的地址;&a是对象(数组)首地址,a+1是数组下一元素的地址,即a[1]&a+1是下一个对象的地址。
6. 如果类的定义如下,则以下代码正确并且良好编程风格是的:
class Object{
public:
virtual~Object(){}
//
};
A std::auto_ptr<Object> pObj(newObject);
解释:
1) auto_ptr的意义
std::auto_ptr是C++标准库里面的模版类, 属于智能指针.当系统异常退出的时候避免资源泄漏(内存)。 其他的资源还对应其他的智能指针。
2) auto_ptr的使用
std::auto_ptr<int> test(new int(1));
test将是一个auto_ptr的对象,使用一个int指针进行初始化。
test可以象其他指针一样使用,如使用* 使用->但是++不可以使用,以后也许会扩展,其实难对++做越界管理,也许可以放弃一些速度。 当使用auto_ptr的时候,必须使用显式的类型转化来初始化,如auto_ptr<classA> a(new classA)而不能使用auto_ptr<classA>a = new classA;
3) auto_ptr所有权的转移
auto_ptr对所有权有严格的约定,一个auto_ptr只能控制一个指针,不能控制多个,当auto_ptr拥有一个指针的时候就不能在拥有其他的指针了。同时,不同的auto_ptr不能拥有同一个指针。
7. 三个基本元素:
1). 封装:封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
2). 继承:继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承。新类继承了原始类的特性,新类称为原始类的派生类(子类),而原始类称为新类的基类(父类)。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
3). 多态:多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性。多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
C++ 中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术, RTTI 技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。
五个基本原则:
单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
开放封闭原则(Open-Closed principle):软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
Liskov替换原则(Liskov-SubstituionPrinciple):子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
依赖倒置原则(Dependecy-Inversion Principle):依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
接口隔离原则(Interface-Segregation Principle):使用多个小的专门的接口,而不要使用一个大的总接口。