delete 会调用对象的析构函数 , 和 new 对应 free 只会释放内存, new 调用构造函数。 malloc 与 free 是 C++/C 语言的标准库函数, new/delete 是 C++ 的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用 maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于 malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于 malloc/free 。因此 C++ 语言需要一个能完成动态内存分配和初始化工作的运算符 new ,以及一个能完成清理与释放内存工作的运算符 delete 。注意 new/delete 不是库函数。
总结:new和delete会自动调用对象的构造与析构函数而malloc与free不会;
new和delete式C++运算符,而malloc和free是C/C++标准库函数。
——————————————————————————————–
2.delete 与 delete [] 区别
delete 只会调用一次析构函数,而 delete[] 会调用每一个成员的析构函数。在 More Effective C++ 中有更为详细的解释:“当 delete 操作符用于数组时,它为每个数组元素调用析构函数,然后调用 operatordelete 来释放内存。” delete 与 New 配套, delete [] 与 new [] 配套
MemTest*mTest1=newMemTest[10];
MemTest*mTest2=newMemTest;
int*pInt1=newint[10];
int*pInt2=newint;
delete[]pInt1; //-1-
delete[]pInt2; //-2-
delete[]mTest1;//-3-
delete[]mTest2;//-4-
在 -4- 处报错。
这就说明:对于内建简单数据类型, delete 和 delete[] 功能是相同的。对于自定义的复杂数据类型, delete 和 delete[] 不能互用。 delete[] 删除一个数组, delete 删除一个指针简单来说,用 new 分配的内存用 delete 删除用 new[] 分配的内存用 delete[] 删除 delete[] 会调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。如果你在用 delete 时没用括号, delete 就会认为指向的是单个对象,否则,它就会认为指向的是一个数组。
总结: delete 只会调用一次析构函数,而 delete[] 会调用每一个成员的析构函数。
——————————————————————————————–
3.C C++ JAVA 共同点,不同之处?
相同点:都是面向对象的语言
不同点:c/c++是编译型语言,还有一些语言完全是解释型的(如Basie),而java既是编译型的又是解释型的语言
c/c++存在指针运算,Basie没有显示指针,而java有指针,但取消了指针的运算
——————————————————————————————–
4. 继承优缺点。
类继承是在编译时刻静态
定义的,且可直接使用,类继承可以较方便地改变父类的实现。但是类继承也有一些不足之处。首先,因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。更糟的是,父类通常至少定义了子类的部分行为,父类的任何改变都可能影响子类的行为。如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系了灵活性并最终了复用性。
——————————————————————————————–
5.C++ 有哪些性质(面向对象特点)
封装,继承和多态。
在面向对象程序设计语言中,封装是利用可重用成分构造软件系统的特性,它不仅支持系统的可重用性,而且还有利于提高系统的可扩充性;消息传递可以实现发送一个通用的消息而调用不同的方法;封装是实现信息隐蔽的一种技术,其目的是使类的定义和实现分离。
——————————————————————————————–
6. 子类析构时要调用父类的析构函数吗?
析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候 , 派生类的信息已经全部销毁了定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数 JAVA 无析构函数深拷贝和浅拷贝
——————————————————————————————–
7. 多态,虚函数,纯虚函数
这么一大堆名词,实际上就围绕一件事展开,就是多态,其他三个名词都是为实现C++的多态机制而提出的一些规则,下面分两部分介绍,第一部分介绍【多态】,第二部分介绍【虚函数,纯虚函数,抽象类】
一 【多态】
多态的概念 :关于多态,好几种说法,好的坏的都有,分别说一下:
1 指同一个函数的多种形态。
个人认为这是一种高手中的高手喜欢的说法,对于一般开发人员是一种差的不能再差的概念,简直是对人的误导,然人很容易就靠到函数重载上了。
以下是个人认为解释的比较好的两种说法,意思大体相同:
2多态是具有表现多种形态的能力的特征,在OO中是指,语言具有根据对象的类型以不同方式处理之,特别是重载方法和继承类这种形式的能力。
这种说法有点绕,仔细想想,这才是C++要告诉我们的。
3多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给
父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数(Virtual Function) 实现的。
这种说法看来是又易懂,又全面的一种,尤其是最后一句,直接点出了虚函数与多态性的关系,如果你还是不太懂,没关系,再把3读两遍,有个印象,往后看吧。
- – - – - – - – - – - – - – - – - – - – - – - – - – - – - – -
二 【虚函数,纯虚函数,抽象类】
多态才说了个概念,有什么用还没说就进入第二部分了?看看概念3的最后一句,虚函数就是为多态而生的,多态的作用的介绍和虚函数简直关系太大了,就放一起说吧。
多态的作用:继承是子类使用父类的方法,而多态则是父类使用子类的方法。这是一句大白话,多态从用法上就是要用父类(确切的说是父类的对象名)去调用子类的方法,例如:
【例一】
class A {
public:
A() {}
(virtual) void print() {
cout << “This is A.” << endl;
}
};
class B : public A {
public:
B() {}
void print() {
cout << “This is B.” << endl; } }; int main(int argc, char* argv[]) { B b; A a; a = b;a.print;—————————————- make1 // A &a = b; a->print();———————————-make2
//A *a = new B();a->print();——————————–make3
return 0;
}
这将显示:
This is B.
如果把virtual去掉,将显示:
This is A.
(make1,2,3分别是对应兼容规则(后面介绍)的三种方式,调用结果是一样的)
加上virtual ,多态了,B中的print被调用了,也就是可以实现父类使用子类的方法。
对多态的作用有一个初步的认识了之后,再提出更官方,也是更准确的对多态作用的描述:
多态性使得能够利用同一类(基类)类型的指针来引用不同类的对象,以及根据所引用对象的不同,以不同的方式执行相同的操作。把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(也就是可以调用子对象中对父对象的相关函数的改进方法)。
那么上面例子中为什么去掉virtual就调用的不是B中的方法了呢,明明把B的对象赋给指针a了啊,是因为C++定义了一组对象赋值的兼容规则,就是指在公有派生的情况下,对于某些场合,一个派生类的对象可以作为基类对象来使用,具体来说,就是下面三种情形:
Class A ;
class B:public A
1. 派生的对象可以赋给基类的对象
A a;
B b;
a = b;
2. 派生的对象可以初始化基类的引用
B b;
A &a = b;
3. 派生的对象的地址可以赋给指向基类的指针
B b;
A *a = &b;
或
A *a = new B();
由上述对象赋值兼容规
则可知,一个基类的对象可兼容派生类的对象,一个基类的指针可指向派生类的对象,一个基类的引用可引用派生类的对象,于是对于通过基类的对象指针(或引用)对成员函数的调用,编译时无法确定对象的类,而只是在运行时才能确定并由此确定调用哪个类中的成员函数。
看看刚才的例子,根据兼容规则,B的对象根本就被当成了A的对象来使用,难怪B的方法不能被调用。
【例二】
#include
using namespace std;
class A
{
public:
void (virtual) print(){cout << “A print”<
private:
};
class B : public A
{
public:
void print(){cout << “B print”< private:
};
void test(A &tmpClass)
{
tmpClass.print();
}
int main(void)
{
B b;
test(b);
getchar();
return 0;
}
这将显示:
B print
如果把virtual去掉,将显示:
A print
那么,为什么加了一个virtual以后就达到调用的目的了呢,多态了嘛~那么为什么加上virtual就多态了呢,我们还要介绍一个概念:联编
函数的联编:在编译或运行将函数调用与相应的函数体连接在一起的过程。
1 先期联编或静态联编:在编译时就能进行函数联编称为先期联编或静态联编。
2 迟后联编或动态联编:在运行时才能进行的联编称为迟后联编或动态联编。
那么联编与虚函数有什么关系呢,当然,造成上面例子中的矛盾的原因就是代码的联编过程采用了先期联编,使得编译时系统无法确定究竟应该调用基类中的函数还是应该调用派生类中的函数,要是能够采用上面说的迟后联编就好了,可以在运行时再判断到底是哪个对象,所以,virtual关键字的作用就是提示编译器进行迟后联编,告诉连接过程:“我是个虚的,先不要连接我,等运行时再说吧”。
那么为什么连接的时候就知道到底是哪个对象了呢,这就引出虚函数的原理了:当编译器遇到virtual后,会为所在的类构造一个表和一个指针,那个表叫做vtbl,每个类都有自己的vtbl,vtbl的作用就是保存自己类中虚函数的地址,我们可以把vtbl形象地看成一个数组,这个数组的每个元素存放的就是虚函数的地址.指针叫做vptr,指向那个表。而这个指针保存在相应的对象当中,也就是说只有创建了对象以后才能找到相应虚函数的地址。
【注意】
1为确保运行时的多态定义的基类与派生类的虚函数不仅函数名要相同,其返回值及参数都必须相同,否则即使加上了virtual,系统也不进行迟后联编。
2 虚函数关系通过继承关系自动传递给基类中同名的函数,也就是上例中如果A中print有virtual,那么 B中的print即使不加virtual,也被自动认为是虚函数。
*3 没有继承关系,多态机制没有意义,继承必须是公有继承。
*4现实中,远不只我举
的这两个例子,但是大的原则都是我前面说到的“如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的”。这句话也可以反过来说:“如果你发现基类提供了虚函数,那么你最好override它”。
纯虚函数:
虚函数的作用是为了实现对基类与派生类中的虚函数成员的迟后联编,而纯虚函数是表明不具体实现的虚函数成员,即纯虚函数无实现代码。其作用仅仅是为其派生类提过一个统一的构架,具体实现在派生类中给出。
一个函数声明为纯虚后,纯虚函数的意思是:我是一个抽象类!不要把我实例化!纯虚函数用来规范派生类的行为,实际上就是所谓的“接口”。它告诉使用者,我的派生类都会有这个函数。
抽象类:
含有一个或多个纯虚函数的类称为抽象类。
【例三】
#include
using namespace std;
class A
{
public:
virtual float print() = 0;
protected:
float h,w;
private:
};
class B : public A
{
public:
B(float h0,float w0){h = h0;w = w0;}
float print(){return h*w;}
private:
};
class C : public A
{
public:
C(float h0,float w0){h = h0;w = w0;}
float print(){return h*w/2;}
private:
};
int main(void)
{
A *a1,*a2;
B b(1,2);
C c(1,2);
a1 = &b;
a2 = &c;
cout << a1->print() 【注意】 1 抽象类并不能直接定义对象,只可以如上例那样声明指针,用来指向基类派生的子类的对象,上例中的A *a1,*a2;该为 A a1,a2;是错误的。 2 从一个抽象类派生的类必须提供纯虚函数的代码实现或依旧指明其为派生类,否则是错误的。 3 当一个类打算被用作其它类的基类时,它的析构函数必须是虚的。 【例三】 class A { public: A() { ptra_ = new char[10];} ~A() { delete[] ptra_;} // 非虚析构函数 private: char * ptra_; }; class B: public A { public: B() { ptrb_ = new char[20];} ~B() { delete[] ptrb_;} private: char * ptrb_; }; void foo() { A * a = new B; delete a; } 在这个例子中,程序也许不会象你想象的那样运行,在执行delete a的时候,实际上只有A::~A()被调用了,而B类的析构函数并没有被调用!这是否有点儿可怕? 如果将上面A::~A()改为virtual,就可以保证B::~B()也在delete a的时候被调用了。因此基类的析构函数都必须是virtual的。纯虚的析构函数并没有什么作用,是虚的就够了。通常只有在希望将一个类变成抽象类(不能实例化的类),而这个类又没有合 适的函数可以被纯虚化的时候,可以使用纯虚的析构函数来达到目的。 最后通过一个例子说明一下抽象类,纯虚函数以及多态的妙用吧: 我们希望通过一个方法得到不同图形面积的和的方式: #include using namespace std; class A //定义一个抽象类,用来求图形面积 { public: virtual float area() = 0;//定义一个计算面积的纯虚函数,图形没确定,当 //不能确定具体实现 protected: float h,w; //这里假设所有图形的面积都可以用h和w两个元素计算得出 //就假设为高和长吧 private: }; class B : public A //定义一个求长方形面积的类 { public: B(float h0,float w0){h = h0;w = w0;} float area (){return h*w;}//基类纯虚函数的具体实现 private: }; class C : public A //定义一个求三角形面积的类 { public: C(float h0,float w0){h = h0;w = w0;} float area (){return h*w/2;}//基类纯虚函数的具体实现 private: }; float getTotal(A *s[],int n)//通过一个数组传递所有的图形对象 //多态的好处出来了吧,不是多态,不能用基类A调用 //参数类型怎么写,要是有100个不同的图形,怎么传递 { float sum = 0; for(int i = 0;i < n; i++) sum = sum + s[i]->area(); return sum; } int main(void) { float totalArea; A *a[2]; a[0] = new B(1,2); //一个长方形对象 a[1] = new C(1,2);//一个三角形对象 totalArea = getTotal(a , 2);//求出两个对象的面积和 getchar(); return 0; } ——————————————————————————————– 8. 求下面函数的返回值(微软) int func(x) { int countx = 0; while(x) { countx ++; x = x&(x-1); } return countx; } 假定 x = 9999 。 答案: 8 思路:将 x 转化为 2 进制,看含有的 1 的个数。 ——————————————————————————————– 9. 什么是 “ 引用 ” ?申明和使用 “ 引用 ” 要注意哪些问题? 答:引用就是某个目标变量的 “ 别名 ”(alias) ,对应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。 ——————————————————————————————– 10. 将 “ 引用 ” 作为函数参数有哪些特点? ( 1 )传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用, 所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。 ( 2 )使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。 ( 3 )使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用 “* 指针变量名 ” 的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。 ——————————————————————————————– 11. 在什么时候需要使用 “ 常引用 ” ? 如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式: const 类型标识符 & 引用名 = 目标变量名; 例 1 int a ; const int &ra=a; ra=1; // 错误 a=1; // 正确 例 2 string foo( ); void bar(string & s); 那么下面的表达式将是非法的: bar(foo( )); bar(“hello world”); 原因在于 foo( ) 和 “hello world” 串都会产生一个临时对象,而在 C++ 中,这些临时对象都是 const 类型的。因此上面的表达式就是试图将一个 const 类型的对象转换为非 const 类型,这是非法的。引用型参数应该在能被定义为 const 的情况下,尽量定义为 const 。 ——————————————————————————————– 12. 将 “ 引用 ” 作为函数返回值类型的格式、好处和需要遵守的规则 ? 格式:类型标识符 & 函数名(形参列表及类型说明) { // 函数体 } 好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生 runtime error! 注意事项: ( 1 )不能返回局部变量的引用。这条可以参照 Effective C++[1] 的 Item 31 。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了 ” 无所指 ” 的引用,程序会进入未知状态。 ( 2 )不能返回函数内部 new 分配的内存的引用。这条可以参照 Effective C++[1] 的 Item 31 。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部 new 分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用 用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由 new 分配)就无法释放,造成 memory leak 。 ( 3 )可以返回类成员的引用,但最好是 const 。这条原则可以参照 Effective C++[1] 的 Item 30 。主要原因是当对象的属性是与某种业务规则( business rule )相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。 ( 4 )流操作符重载返回值申明为 “ 引用 ” 的作用: 流操作符 << 和 >> ,这两个操作符常常希望被连续使用,例如: cout << “hello” << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个 << 操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用 << 操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是 C++ 语言中引入引用这个概念的原因吧。 赋值操作符 = 。这个操作符象流操作符一样,是可以连续使用的,例如: x = j = 10; 或者 (x=10)=100; 赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。 例 3 # i nclude int &put(int n); int vals[10]; int error=-1; void main() { put(0)=10; // 以 put(0) 函数值作为左值,等价于 vals[0]=10; put(9)=20; // 以 put(9) 函数值作为左值,等价于 vals[9]=20; cout< cout<=0 && n else { cout< 6″) : puts(” return 0; } ——————————————————————————————– 114. 交换两个数的宏定义 交换两个参数值的 宏定义 为: . #define SWAP (a,b) (a)=(a)+(b);(b)=(a)-(b);(a)=(a)-(b); ——————————————————————————————– 115.Itearator 各指针的区别 游标和指针 我说过游标是指针,但不仅仅是指针。游标和指针很像,功能很像指针,但是实际上,游标是通过重载一元的 ”*” 和 ”->” 来从容器中间接地返回一个值。将这些值存储在容器中并不是一个好主意,因为每当一个新值添加到容器中或者有一个值从容器中删除,这些值就会失效。在某种程度上,游标可以看作是句柄( handle )。通常情况下游标( iterator )的类型可以有所变化,这样容器也会 有几种不同方式的转变: iterator—— 对于除了 vector 以外的其他任何容器,你可以通过这种游标在一次操作中在容器中朝向前的方向走一步。这意味着对于这种游标你只能使用 “++” 操作符。而不能使用 “–” 或 “+=” 操作符。而对于 vector 这一种容器,你可以使用 “+=” 、 “—” 、 “++” 、 “-=” 中的任何一种操作符和 “” 、 “>=” 、 “==” 、 “!=” 等比较运算符。 ——————————————————————————————– 116. C++ 中的 class 和 struct 的区别 从语法上,在 C++ 中(只讨论 C++ 中)。 class 和 struct 做类型定义时只有两点区别: (一)默认继承权限。如果不明确指定,来自 class 的继承按照 private 继承处理,来自 struct 的继承按照 public 继承处理; (二)成员的默认访问权限。 class 的成员默认是 private 权限, struct 默认是 public 权限。 除了这两点, class 和 struct 基本就是一个东西。语法上没有任何其它区别。 不能因为学过 C 就总觉得连 C++ 中 struct 和 class 都区别很大,下面列举的说明可能比较无聊,因为 struct 和 class 本来就是基本一样的东西,无需多说。但这些说明可能有助于澄清一些常见的关于 struct 和 class 的错误认识: ( 1 )都可以有成员函数;包括各类构造函数,析构函数,重载的运算符,友元类,友元结构,友元函数,虚函数,纯虚函数,静态函数; ( 2 )都可以有一大堆 public/private/protected 修饰符在里边; ( 3 )虽然这种风格不再被提倡,但语法上二者都可以使用大括号的方式初始化: A a = {1, 2, 3}; 不管 A 是个 struct 还是个 class ,前提是这个类 / 结构足够简单,比如所有的成员都是 public 的,所有的成员都是简单类型,没有显式声明的构造函数。 ( 4 )都可以进行复杂的继承甚至多重继承,一个 struct 可以继承自一个 class ,反之亦可;一个 struct 可以同时继承 5 个 class 和 5 个 struct ,虽然这样做不太好。 ( 5 )如果说 class 的设计需要注意 OO 的原则和风格,那么没任何理由说设计 struct 就不需要注意。 ( 6 )再次说明,以上所有说法都是指在 C++ 语言中,至于在 C 里的情况, C 里是根本没有 “class” ,而 C 的 struct 从根本上也只是个包装数据的语法机制。 ————————————————————— 最后,作为语言的两个关键字,除去定义类型时有上述区别之外,另外还有一点点: “class” 这个关键字还用于定义模板参数,就像 “typename” 。但关键字 “struct” 不用于定义模板参数。 在模版中,类型参数前面可以使用class或typename,如果使用struct,则含义不同,st ruct后面跟的是“non-type template parameter”,而class或typename后面跟的是类型参数。 template void f(X x) { } //出错信息:d:codecpptestcpptestcpptest.cpp(33) : error C2065: ‘X’ : undeclared identifier 关于使用大括号初始化 class 和 struct 如果定义了构造函数的话,都不能用大括号进行初始化 如果没有定义构造函数, struct 可以用大括号初始化。 如果没有定义构造函数,且所有成员变量全是 public 的话,可以用大括号初始化。 关于默认访问权限 class 中默认的成员访问权限是 private 的,而 struct 中则是 public 的。 关于继承方式 class 继承默认是 private 继承,而 struct 继承默认是 public 继承。 关于模版 在模版中,类型参数前面可以使用 class 或 typename ,如果使用 struct ,则含义不同, struct 后面跟的是 “non-type template parameter” ,而 class 或 typename 后面跟的是类型参数。 class 中有个默认的 this 指针, struct 没有 不同点:构造函数,析构函数 this 指针 ——————————————————————————————– 117. 有关重载函数 返回值类型不同构不成重载 参数参数顺序不同能构成重载 c++ 函数同名不同返回值不算重载!函数重载是忽略返回值类型的。 成员函数被重载的特征有: 1) 相同的范围(在同一个类中); 2) 函数名字相同; 3) 参数不同; 4) virtual 关键字可有可无。 5) 成员函数中 有无 const ( 函数后面 ) 也可判断是否重载 ——————————————————————————————– 118. 数据库与 T-SQL 语言 关系数据库是表的集合,它是由一个或多个关系模式定义。 SQL 语言中的数据定义功能包括对数据库、基本表、视图、索引的定义。 ——————————————————————————————– 119. 关系模型的基本概念 关系数据库以关系模型为基础,它有以下三部分组成: ● 数据结构 —— 模型所操作的对象、类型的集合 ● 完整性规则 —— 保证数据有效、正确的约束条件 ● 数据操作 —— 对模型对象所允许执行的操作方式 关系( Relation )是一个由行和列组成的二维表格,表中的每一行是一条记录( Record ),每一列是记录的一个字段( Field )。表中的每一条记录必须是互斥的,字段的值必须具有原子性。 ——————————————————————————————– 120.SQL 语言概述 SQL (结构化查询语言)是关系数据库语言的一种国际标准,它是一种非过程化的语言。通过编写 SQL ,我们可以实现对关系数据库的全部操作。 ● 数据定义语言( DDL ) —— 建立和 管理数据库对象 ● 数据操纵语言( DML ) —— 用来查询与更新数据 ● 数据控制语言( DCL ) —— 控制数据的安全性 起来是一个很简单的问题,每一个使用过 RDBMS 的人都会有一个概念。 事务处理系统的典型特点是具备 ACID 特征。 ACID 指的是 Atomic (原子的)、 Consistent (一致的)、 Isolated (隔离的)以及 Durable (持续的),它们代表着事务处理应该具备的四个特征: 原子性:组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分 一致性:在事务处理执行之前和之后,数据是一致的。 隔离性:一个事务处理对另一个事务处理没有影响。 持续性:当事务处理成功执行到结束的时候,其效果在数据库中被永久纪录下来。 ——————————————————————————————– 121.C 语言中结构化程序设计的三种基本控制结构 顺序结构 选择结构 循环结构 ——————————————————————————————– 122.CVS 是什么 cvs ( Concurrent Version System ) 是一个版本控制系统。使用它,可以记录下你的源文件的历史。 例如,修改软件时可能会不知不觉混进一些 bug ,而且可能过了很久你才会察觉到它们的存在。有了 cvs ,你可以很容易地恢复旧版本,并从中看出到底是哪个修改导致了这个 bug 。有时这是很有用的。 CVS 服务器端对每个文件维护着一个修订号 , 每次对文件的更新,都会使得文件的修订号加 1 。在客户端中也对每个文件维护着一个修订号 ,CVS 通过这两个修订号的关系,来进行 Update,Commit 和发现冲突等操作操作 ——————————————————————————————– 123. 三种基本的数据模型 按照数据结构类型的不同,将数据模型划分为层次模型、网状模型和关系模型。 ——————————————————————————————– 同类其他面试题 点击新一篇或旧一篇可浏览全部同类面试题 @@1 ·由于虚拟机从第一个匹配的catch子句处继续执行,所以catch子句表中的顺序是很重要的。因为Java代码是结构化的,因此总可以把某个方法的所有的异常处理器都按序排列到一个表中,对任意可能的程序计数器的值,都可以用线性的顺序找到合适的异常处理块,以处理在该程序计数器值下发生的异常情况。 ·如果找不到匹配的catch子句,那么当前方法得到一个”未截获异常”的结果并返回到当前方法的调用者,好像异常刚刚在其调用者中发生一样。如果在调用者中仍然没有找到相应的异常处理块,那么这种错误传播将被继续下去。如 果错误被传播到最顶层,那么系统将调用一个缺省的异常处理块。 @@2 strcpy extern char *strcpy(char *dest,char *src); 用法:#include { if ((strDest==NULL)||(strSrc==NULL)) //[1] throw “Invalid argument(s)”; //[2] char * strDestCopy=strDest; //[3] while ((*strDest++=*strSrc++)!=’\\0′); //[4] return strDestCopy; } @@3 JVM在整个jdk中处于最底层,负责与操作系统的交互,用来屏蔽操作系统环境,提供一个完整的java运行环境,因此也就 虚拟了计算机,操作系统装入JVM是通过jdk中的java.exe来完成的,通过以下4步: 1,创建JVM装在环境和配置 2,装载JVM.dll 3,初始化JVM.dll并挂载到JNIENV(JNI调用接口)实例。 4,调用JNIENV实例,装载并处理class类。 @@4 volatile @@5 SIZEOF @@6 函数指针、指针函数、数组指针、指针数组 @@7 STL = Standard Template Library vector,map @@8 正则表达式 @@9 SQL @@10 指针数组、数组指针 @@11 #define Max(a,b) ( a-b)?a:b @@ 12 ping过程 @@ 13操作系统lru存储算法 @@ 14深度广度 @@ 15 内存泄露 内存溢出指你申请了10个字节的空间,但是你在这个空间写入11或以上字节的数据,就是溢出内存泄漏指你用malloc或new申请了一块内存,但是没有通过free或delete将内存释放,导致这块内存一直处于占用状态 内存泄露是指程序中间动态分配了内存,但是在程序结束时没有释放这部分内存,从而造成那一部分内存不可用的情况,重起计算机可以解决,但是也有可能再次发生内存泄露,内存泄露和硬件没有关系,它是由软件引起的。 一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。 @@ 16 char* p = “12345 “; printf( “%c “, *p++); printf( “%c “, *p++); 为什么输出 1, 2, 而不是 2, 3 ? 答: 对于表达式中的++而言,记住一个顺序: 后置++在表达式后求值 前置++在表达式前求值 因此题目变为: char* p = “12345 “; printf( “%c “, *p); p++; printf( “%c “, *p); p++; @@ 17 基类指针、派生类指针与他们的关系 基类指针可以指向基类对象; 派生类指针可以指向派生类对象; 基类指针可以指 向派生类对象(无需强制转换),但只能引用基类中有的成员; 派生类指针指向基类对象,会引发语法错误。但派生类指针通过强制转换可指向基类对象。可是该动作很危险:若引用对象中不村在的派生类成员,会得到错误的结果。 @@ 18 malloc/free是c/C++语言的标准库函数;new/delete是c++的运算符,都可以用申请动态内存和释放内存。 对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建是要自动执行Constructor函数,对象在消亡之前要自动执行Destructor函数。由于malloc/free是库函数不是运算符,不在编译器控制权限之内,不能够把Constructor及Destructor任务加于malloc/free. @@ 19 1 ) main() { int a=3;printf(“%d\ ”,(a=4*5,a*2));} a=4*5—结果a为20 (a=4*5,a*2)—结果为a*2,即40 所以,结果是40 2 ) main() { printf(“Hello world !\ ”); int a = 6; printf(“—–%d\ ”,a=(2*44,a*3)); system(“pause”); } a输出为18; 3 ) main() { printf(“Hello world !\ ”); int a; printf(“—–%d\ ”,a=(2*44,a*34,a*33,a*3)); system(“pause”); } @@ 20 为什么析构函数要声明成virtual 对象构造的顺序是先基类,再派生类. 析构的顺序是先派生类,再基类. 如果基类没有虚函数,则不存在多态,在用基类指针/引用绑定到一个派生类对象时,该对象就被当成了基类对象,也就是对象被截断了. 所以delete基类指针时,直接调用基类析构函数,从而派生类对象没有得到释放. 为了保证在继承关系中保证派生类可以正确的释放,所以通常把可能要被继承的类的析构函数写成虚拟函数(每个类都会有析构函数).除非你保证该类不会被继承. 而如果有其他成员函数已经是虚拟函数了,那么析构函数是否虚拟都无关紧要了. @@21 c++函数调用前必须声明吗 无论C还是C++,在函数调用之前如果函数没有定义,都要先声明! 可能有的编译器有所谓隐式声明的語法。就是默认地认为那个函数的参数就是你传的参数,并且函数返回 int。不过无论如何,显式声明都是个好习惯,不仅编译器不用揣测函数类型,而且看的人也能看的清楚些。 吗 1. C中static有什么作用? (1)隐藏。 当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性,故使用static在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。 (2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量。 (3)static的第三个作用是默认初始化为0。其实全局变量也具备这一属性,因为 为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0×00,某些时候这一特点可以减少程序员的工作量。 2.C++中const有什么用?不要一听到const就说是常量,这样给考官一种在和一个外行交谈的感觉。应该说const修饰的内容不可改变就行了, 定义常量只是一种使用方式而已,还有const数据成员,const参数, const返回值, const成员函数等, 被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。 3. C与C++各自是如何定义常量的?有什么不同?C中是使用宏#define定义, C++使用更好的const来定义。 区别:1)const是有数据类型的常量,而宏常量没有,编译器可以对前者进行静态类型安全检查,对后者仅是字符替换,没有类型安全检查,而且在字符替换时可能会产生意料不到的错误(边际效应)。2)有些编译器可以对const常量进行调试, 不能对宏调试。 4. 既然C++中有更好的const为什么还要使用宏?const无法代替宏作为卫哨来防止文件的重复包含。 5. C++中引用和指针的区别?引用是对象的别名, 操作引用就是操作这个对象, 必须在创建的同时有效得初始化(引用一个有效的对象, 不可为NULL), 初始化完毕就再也不可改变, 引用具有指针的效率, 又具有变量使用的方便性和直观性, 在语言层面上引用和对象的用法一样, 在二进制层面上引用一般都是通过指针来实现的, 只是编译器帮我们完成了转换. 之所以使用引用是为了用适当的工具做恰如其分的事, 体现了最小原则. 6. 说一说C与C++的内存分配方式?1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,如全局变量,static变量。2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。3)从堆上分配(动态内存分配)程序在运行的时候用malloc或new申请任意多少的内存,程序员负责在何时用free或delete释放内存。动态内存的生存期自己决定,使用非常灵活。 7. new/delete 与 malloc()/free() 的区别?malloc() 与 free() 是C语言的标准库函数, new/delete 是C++的运算符, 他们都可以用来申请和释放内存, malloc()和free()不在编译器控制权限之内, 不能把构造函数和析构函数的任务强加给他们. 8. #include 9. 在C++ 程序中调用被 C编译器编译后的 函数,为什么要加 extern “C”? C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为: void foo(int x, int y);该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。C++提供了C连接交换指定符号extern“C”来解决名字匹配问题。 10. C++中的什么是多态性? 是如何实现的?多态性是面向对象程序设计语言继数据抽象和继承之后的第三个基本特征。它是在运行时出现的多态性通过派生类和虚函数实现。基类和派生类中使用同样的函数名, 完成不同的操作具体实现相隔离的另一类接口,即把“ w h a t”从“h o w”分离开来。多态性提高了代码的组织性和可读性,虚函数则根据类型的不同来进行不同的隔离。 11. 什么是动态特性?在绝大多数情况下, 程序的功能是在编译的时候就确定下来的, 我们称之为静态特性. 反之, 如果程序的功能是在运行时刻才能确定下来的, 则称之为动态特性。C++中, 虚函数,抽象基类, 动态绑定和多态构成了出色的动态特性。 12.什么是封装?C++中是如何实现的?封装来源于信息隐藏的设计理念, 是通过特性和行为的组合来创建新数据类型让接口与具体实现相隔离。C++中是通过类来实现的, 为了尽量避免某个模块的行为干扰同一系统中的其它模块,应该让模块仅仅公开必须让外界知道的接口. 13. 什么是RTTI?RTTI事指运行时类型识别(Run-time type identification)在只有一个指向基类的指针或引用时确定一个对象的准确类型。 14. 什么是拷贝构造函数?它是单个参数的构造函数,其参数是与它同属一类的对象的(常)引用;类定义中,如果未提供自己的拷贝构造函数,C++提供一个默认拷贝构造函数,该默认拷贝构造函数完成一个成员到一个成员的拷贝 15. 什么是深浅拷贝?浅拷贝是创建了一个对象用一个现成的对象初始化它的时候只是复制了成员(简单赋值)而没有拷贝分配给成员的资源(如给其指针变量成员分配了动态内存); 深拷贝是当一个对象创建时,如果分配了资源,就需要定义自己的拷贝构造函数,使之不但拷贝成员也拷贝分配给它的资源. 16.面向对象程序设计的优点?开发时间短, 效率高, 可靠性高。面向对象编程的编码具有高可重用性,可以在应用程序中大量采用成熟的类库(如STL),从而虽短了开发时间,软件易于维护和升级。 很多公司都问到了二维指针,二维数组,以及指向数组的指针的相关问题,在此,简单的给大家总结下,为大家在面试的时候增加一定的信心。 这类题目,总的来说,实际上可以归纳为如下几点: 1、 有一个一维数组int a1[10],定义一个指针,指向一维数组名。 2、 有一个一维数组int a2[10],定义一个指针,指向一维数组名的地址。 3、 有一个二维数组int a3[3][4],定义一个指针,指向二维数组名。 4、 有一个二维数组int a4[3][4],定义一个指针,指向二维数组名的地址。 对于这类题目,刚看到,似乎感觉比较没有思路,其实我们细心思考下,这四个问题,都是让我们定义一个指针,指向一片连续的内存空间,只是说这片内存空间有的是一维,有的是二维而已。 我们这次先分析下前面两道题: 那我们先看第一个题目,因为a1本身就是连续空间的首地址,那么我们就只需要定义一个地址变量p,让p = a就可以了,既然要用“=”,那么等号两边就需要的类型一样,所以毫无疑问,p应该为 <数据类型> *p了。现在不确定的就是数据类型了。根据指针的定义,应该有p[0]到p[1]的偏移和a[0]到a[1]的偏移一致,我们很容易看出,a[0]和a[1]的偏移是int类型。那么对于第一个题目的数据类型就应该为int了,第一题的答案为int *p; 接着看第二问题,这个问题的难点在,什么叫数组名的地址,我们都知道C语言中的变量都有地址,但数组名是什么,他是不是也在内存中实实在在的存在哪,回答是否定的(感兴趣的同学,可以试着证明下,a的值和&a的值有什么特点)。a是数组空间的一个标号,那对标号取地址,是什么那,我们可以试着把&a和&a+1的地址都打印出来,然后看下他们的关系,我们会发现,&a和&a+1之间的偏移实际上是10个int的单位。那么我们也可以推出数组名的地址原来标示了整个数组的空间。也就是说&a[0]和&a[1]的偏移实际上是一个int [10]的空间。那么对于<数据类型> *p来说,数据类型就是指int [10]这个空间了。第二题的答案为int (*p)[10]; 对于前2个问题,可以写出如下实验代码: int main(int argc,char *argv[]) { int a1[10]; int *p; int (*p1)[10]; printf(“the address of a1[0] is %p,the address of a1[1] is %p\ ”,&a1[0],&a1[1]); printf(“the &a1[0] is %p,the &a[1] is %p\ ”,&(&a1)[0],&(&a1)[1]); p = a1; p1 = &a1; } 以上程序中p1,p即我们应该填的,如果编译器编译时没有警告,那么说明我们的这样写法是正确的。 那么在深入下的分析下,p1和p现在都要他访问a1中的第二个元素,即a1[1],应该怎么做那? 大家可以思考下,答案是: p[1] p1[0][1],如何验证那,只要执行if(p[1] = = p1[0][1]),如果这个逻辑为真,那说明正确。 上次题目的内容为: 1、 有一个一维数组int a1[10],定义一个指针,指向一维数组名。 2、 有一个一维数组int a2[10],定义一个指针,指向一维数组名的地址。 3、 有一个二维数 组int a3[3][4],定义一个指针,指向二维数组名。 4、 有一个二维数组int a4[3][4],定义一个指针,指向二维数组名的地址。 还记得前2道题的分析方法吗,第一个题,应该定义一个int *p的指针来指向一维数组a1(这个是比较简单的)。而第二个题目为定义int (*p)[10]来指向一维数组a2的地址。 我们这次先分析后面面两道题: int a3[3][4]为二维数组,大家都知道他的物理意义是3行4列,但是我们都知道,计算机的内存是一维空间,即不可能按照一个面的方式存储二维数组的,那么计算机怎么存储呢,大家可以参考下C语言教材,二维数组是按照从上到下从左到右的顺序放置到一维内存空间的。 比如int a3[3][4] = {{1,2,3,4},{5,6,7,8}},在计算机分配了8个int空间按顺序存储1,2,3,4,5,6,7,8,那计算机怎么知道1,2,3,4为第一行那,而不是1,2为一行,3,4为一行那。看来a3中的“[4]”起到了很重要的作用。 那么我们还是回到题目上,定义一个指针指向a3,那么我们就先看下a3是怎么读内存的?a3 和a3+1,我们分别用%p来查看下他们的地址偏移,程序大家可以自己写,我们会发现他们两者的差值为16,我们又知道a3里面存着都是4个字节的int类型,那么可以知道,从a3到a3+1偏移了4个int单位,而这个4恰好是第二个方括号的数字。 那么我们定义的指针P偏移内存的方式也应该是4个int单位才能和a3匹配,所以很多同学在面试时给出int **p的结果是不对的,因为二维指针的偏移还是4个字节为一个偏移单位,跟我们分析的不一样。那我们如果去写一个指向4个int单位的指针那,大家可以考虑下使用小括号来帮助我们理解。 答案为:int (*p)[4]。看到这个答案,和我们的第二题的答案很类似,所以我们可以这样认为,一维数组名的地址实质就是一个二维数组的一行。 下面我们看第四题,有涉及到数组名的地址的问题,二维数组名的地址,根据我们上次分析,可以知道,二维数组名的地址偏移实际上是一个二维数组对象,即偏移一个面,那么对于指针的定义,应该容易的得到: int (*p)[3][4]; 这样的答案。 综合这几道题目,我们能更深入的理解关于指针的含义了。下载本文