很经典的C++知识

Overload和Override的区别,Overload方法是否可以改变返回值类型?

答:Overload是重载的意思,Override是覆盖的意思,也就是重写。

(1)重载Overload表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同),重载发生在同一个类中。

(2)重写Override表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。子类覆盖父类的方法时,只能比父类抛出更少的异常,或者是抛出父类抛出的异常的子异常,因为子类可以解决父类的一些问题,不能比父类有更多的问题。子类方法的访问权限只能比父类的更大,不能更小。如果父类的方法是private类型,那么,子类则不存在覆盖的限制,相当于子类中增加了一个全新的方法。重写发生在不同的类(父类和子类)中。

(3)至于Overloaded的方法是否可以改变返回值的类型这个问题,要看你倒底想问什么呢?这个题目很模糊。如果几个Overloaded的方法的参数列表不一样,它们的返回者类型当然也可以不一样。但我估计你想问的问题是:如果两个方法的参数列表完全一样,是否可以让它们的返回值不同来实现重载Overload。这是不行的,我们可以用反证法来说明这个问题,因为我们有时候调用一个方法时也可以不定义返回结果变量,即不要关心其返回结果,例如,我们调用map.remove(key)方法时,虽然remove方法有返回值,但是我们通常都不会定义接收返回结果的变量,这时候假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,java就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。

 轮询任务调度和可抢占式调度有什么区别?

(1)轮询调度的原理是每一次把来自用户的请求轮流分配给内部中的服务器,从1开始,直到N(内部服务器个数),然后重新开始循环。 只有在当前任务主动放弃CPU控制权的情况下(比如任务挂起),才允许其他任务(包括高优先级的任务)控制CPU。其优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。但不利于后面的请求及时得到响应。
(2)抢占式调度允许高优先级的任务打断当前执行的任务,抢占CPU的控制权。这有利于后面的高优先级的任务也能及时得到响应。但实现相对较复杂且可能出现低优先级的任务长期得不到调度。

动态链接库和静态链接库的优缺点

(1) 动态链接库(Dynamic Linked Library):Windows为应用程序提供了丰富的函数调用,这些函数调用都包含在动态链接库中。其中有3个最重要的DLL,Kernel32.dll、 User32.dll和GDI32.dll。有两种使用方式:一种是静态加载,即在应用程序启动时被加载;一种是动态加载,即是该动态链接库在被使用时才被应用程序加载。优点如下:

a. 共享:多个应用程序可以使用同一个动态库,启动多个应用程序的时候,只需要将动态库加载到内存一次即可;
b. 开发模块好:要求设计者对功能划分的比较好。

缺点是不能解决引用计数等问题。

(2)静态库(Static Library):函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件(.EXE文件)。静态链接库作为代码的一部分,在编译时被链接。优缺点如下:
代码的装载速度快,因为编译时它只会把你需要的那部分链接进去,应用程序相对比较大。但是如果多个应用程序使用的话,会被装载多次,浪费内存。

数据库中常用的锁及其应用场景

数据库中的锁是网络数据库中的一个非常重要的概念,它主要用于多用户环境下保证数据库完整性和一致性。各种大型数据库所采用的锁的基本理论是一致的,但在具体实现上各有差别。目前,大多数数据库管理系统都或多或少具有自我调节、自我管理的功能,因此很多用户实际上不 清楚锁的理论和所用数据库中锁的具体实现。在数据库中加锁时,除了可以对不同的资源加锁,还可以使用不同程度的加锁方式,即锁有多种模式,SQL Server中锁模式包括: 

1)共享锁
SQL Server中,共享锁用于所有的只读数据操作。共享锁是非独占的,允许多个并发事务读取其锁定的资源。默认情况下,数据被读取后,SQL Server立即释放共享锁。例如,执行查询“SELECT * FROM my_table”时,首先锁定第一页,读取之后,释放对第一页的锁定,然后锁定第二页。这样,就允许在读操作过程中,修改未被锁定的第一页。但是,事务 隔离级别连接选项设置和SELECT语句中的锁定设置都可以改变SQL Server的这种默认设置。例如,“ SELECT * FROM my_table HOLDLOCK”就要求在整个查询过程中,保持对表的锁定,直到查询完成才释放锁定。 

2)修改锁
修 改锁在修改操作的初始化阶段用来锁定可能要被修改的资源,这样可以避免使用共享锁造成的死锁现象。因为使用共享锁时,修改数据的操作分为两步,首先获得一 个共享锁,读取数据,然后将共享锁升级为独占锁,然后再执行修改操作。这样如果同时有两个或多个事务同时对一个事务申请了共享锁,在修改数据的时候,这些 事务都要将共享锁升级为独占锁。这时,这些事务都不会释放共享锁而是一直等待对方释放,这样就造成了死锁。如果一个数据在修改前直接申请修改锁,在数据修 改的时候再升级为独占锁,就可以避免死锁。修改锁与共享锁是兼容的,也就是说一个资源用共享锁锁定后,允许再用修改锁锁定。 

3)独占锁
独占锁是为修改数据而保留的。它所锁定的资源,其他事务不能读取也不能修改。独占锁不能和其他锁兼容。 

4)结构锁
结构锁分为结构修改锁(Sch-M)和结构稳定锁(Sch-S)。执行表定义语言操作时,SQL Server采用Sch-M锁,编译查询时,SQL Server采用Sch-S锁。 

5)意向锁
意 向锁说明SQL Server有在资源的低层获得共享锁或独占锁的意向。例如,表级的共享意向锁说明事务意图将独占锁释放到表中的页或者行。意向锁又可以分为共享意向锁、 独占意向锁和共享式独占意向锁。共享意向锁说明事务意图在共享意向锁所锁定的低层资源上放置共享锁来读取数据。独占意向锁说明事务意图在共享意向锁所锁定 的低层资源上放置独占锁来修改数据。共享式独占锁说明事务允许其他事务使用共享锁来读取顶层资源,并意图在该资源低层上放置独占锁。 

6)批量修改锁
批量复制数据时使用批量修改锁。可以通过表的TabLock提示或者使用系统存储过程sp_tableoption的“table lock on bulk load”选项设定批量修改锁。 

static关键字

1)函数体内static变量的作用范围为函数体。不同于auto变量。该变量的内存只被分配一次。因此其值在下次调用时仍维持上次的值。
2)在模块内的static全局变量可以被模块内的所有函数访问。但不能被模块外的其他函数访问。
3)在模块内的static函数只可被这一模块内的其它函数调用。这个函数的使用范围被限制在声明它的模块内。
4)在类中的static成员变量属于整个类所有,对类的所有对象只有一份复制。
5)在类中的static成员函数属于整个类所有,这个函数不接受this指针,因而只能访问类的static成员变量。
 

char c = '\72'; 中的\72代表一个字符,72是八进制数,代表ASCII码字符“:”。

10*a++ 中a先进行乘法运算再自增(笔试中经常喜欢出这类运算符优先级容易混淆的输出问题)。

const关键字

1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化。因为以后就没有机会再改变它了。

2)对指针来说,可以指定指针的本身为const,也可以指定指针所指向的数为const。或二者同时为const。

3)在一个函数的声明中,const可以修饰形参,表明它是一个输入参数。在函数内不能改变其值。

4)对于类的成员函数,若指定其为const类型。则表明其是一个常量函数。不能修改类的成员变量。

5)对于类的成员函数,有时候必须指定其返回值为const类型。以使得其返回值不为“左值”。

 

sizeof不是函数而是运算符,所以在计算变量所占用空间大小时,括号是可以省略的,但在计算类型大小时括号则不能省略,比如int i = 0; 则sizeof int是错误的。

 

如果int a[5], 那么a与&a是等价的,因为两者地址相同。
解答:一定要注意a与&a是不一样的,虽然两者地址相同,但意义不一样,&a是整个数组对象的首地址,而a是数组首地址,也就是a[0]的地址,a的类型是int[5],a[0]的类型是int,因此&a+1相当于a的地址值加上sizeof(int) * 5,也就是a[5],下一个对象的地址,已经越界了,而a+1相当于a的地址加上sizeof(int),即a[1]的地址。
 
可作为函数重载判断依据的有:参数个数、参数类型、const修饰符;
不可以作为重载判断依据的有:返回类型。
 
如何将一个小数分解成整数部分和小数部分?
要记得利用头文件中的库函数modf,下面是函数原型(记住一些实用的库函数,避免自己重写):
double modf(double num, double *i); // 将num分解为整数部分*i和小数部分(返回值决定)

 

 

int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p = &(a + 1)[3];
printf("%d\n", *p);

输出:5

说明:因为a+1指向a的第二个元素,[3]表示再向后移动3个元素。

 

程序输出题:

复制代码
 char str1[] = "abc";
 char str2[] = "abc";
 const char str3[] = "abc";
 const char str4[] = "abc";
 const char *str5 = "abc";
 const char *str6 = "abc";
 char *str7 = "abc";
 char *str8 = "abc";
 cout << (str1 == str2) << endl;
 cout << (str3 == str4) << endl;
 cout << (str5 == str6) << endl;
 cout << (str7 == str8) << endl;
复制代码

输出:0 0 1 1

说明:输出str1~str8的地址为:

0x23aa80

0x23aa70

0x23aa60

0x23aa50

0x23aa48

0x23aa40

0x23aa38

0x23aa30

输出str1~str8内容“abc”的存储地址为:

0x23aa80

0x23aa70

0x23aa60

0x23aa50

0x100403030

0x100403030

0x100403030

0x100403030

可以发现str1~str4中的内容是存在栈上,地址各不相同,而str5~str8的内容都是存储在常量区,所以地址都相同。

 

 

char *str = "abc";
printf("%p\n", str1);
cout << &str1 << endl;

上面打印的是字符串 “abc”的地址,下面打印的是 str1 变量的地址。

 

C的结构体和C++结构体的区别(这里就是class了)

(1)C的结构体内不允许有函数存在,C++允许有内部成员函数,且允许该函数是虚函数。所以C的结构体是没有构造函数、析构函数、和this指针的。

(2)C的结构体对内部成员变量的访问权限只能是public,而C++允许public,protected,private三种。

(3)C语言的结构体是不可以继承的,C++的结构体是可以从其他的结构体或者类继承过来的。

以上都是表面的区别,实际区别就是面向过程和面向对象编程思路的区别:
C的结构体只是把数据变量给包裹起来了,并不涉及算法。
而C++是把数据变量及对这些数据变量的相关算法给封装起来,并且给对这些数据和类不同的访问权限。
C语言中是没有类的概念的,但是C语言可以通过结构体内创建函数指针实现面向对象思想
 

如何在类中定义常量成员并为其初始化?

解答:只能在初始化列表里对const成员初始化,像下面这样:
class CBook {
public:
    const double m_price;
    CBook() :m_price(8.8) { }
};

下面的做法是错误的:

复制代码
class CBook {
public:
    const double m_price;
    CBook() {
        m_price = 8.8;
    }
};
复制代码

而下面的做法虽未报错,但有个warning,也不推荐:

class CBook {
public:
    const double m_price = 8.8; // 注意这里若没有const则编译出错
    CBook() { }
};

 

在定义类的成员函数时使用mutable关键字的作用是什么?

解答:当需要在const方法中修改对象的数据成员时,可以在数据成员前使用mutable关键字,防止出现编译出错。例子如下:

复制代码
class CBook {
public:
    mutable double m_price; // 如果不加就会出错
    CBook(double price) :m_price(price) { }
    double getPrice() const; // 定义const方法
};
double CBook::getPrice() const {
    m_price = 9.8;
    return m_price;
}
复制代码

 

构造函数、拷贝构造函数、析构函数的调用点和顺序问题,如下面这个例子输出是什么?

复制代码
class CBook {
public:
    CBook() {
        cout << "constructor is called.\n";
    }
    ~CBook() {
        cout << "destructor is called.\n";
    }
};
 
void invoke(CBook book) { // 对象作为函数参数,如果这里加了个&就不是了,因为加了&后是引用方式传递,形参和实参指向同一块地
                          // 址,就不需要创建临时对象,也就不需要调用拷贝构造函数了
    cout << "invoke is called.\n";
}
 
int main() {
    CBook c;
    invoke(c);
}
复制代码

解答:注意拷贝构造函数在对象作为函数参数传递时被调用,注意是对象实例而不是对象引用。因此该题输出如下:

constructor is called.
invoke is called.
destructor is called. // 在invoke函数调用结束时还要释放拷贝构造函数创建的临时对象,因此这里还调用了个析构函数
destructor is called.

引申:拷贝构造函数在哪些情况下被调用?

(1)函数的参数为类对象且参数采用值传递方式;

(2)将类对象做为函数的返回值。

 

C++中的explicit关键字有何作用?

解答:禁止将构造函数作为转换函数,即禁止构造函数自动进行隐式类型转换。

例如CBook中只有一个参数m_price,在构建对象时可以使用CBook c = 9.8这样的隐式转换,使用explicit防止这种转换发生。

 

在C++中,如果确定了某一个构造函数的创建过程,在该构造函数中如果调用了其它重载的构造函数,它将不会执行其它构造函数的初始化列表部分代码,而是执行函数体代码,此时已经退化成普通函数了。例子说明如下:

复制代码
class CBook {
public:
    double m_price;
    CBook() {
        CBook(8.8);
    }
    CBook(double price) : m_price(price) { }
};
int main() {
    CBook c;
    cout << c.m_price << endl; // 此时并不会输出理想中的8.8
}

 

C++中重载、重写(覆盖)和隐藏的区别

 

具体分析如下:

1.重载:重载从overload翻译过来,是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型

示例代码如下:

?

1
2
3
4
5
6
7
8
class A{
public :
void test( int i);
void test( double i);
void test( int i, double j);
void test( double i, int j);
int test( int i); //错误,非重载
};

前四个互为重载函数,最后一个和第一个不是重载函数。

2.隐藏:隐藏是指派生类的函数屏蔽了与其同名的基类函数。注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏

实例代码如下:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
using namespace std;
class A{
public :
void fun1( int i, int j){
cout << "A::fun1() : " << i << " " << j << endl;
}
};
class B : public A{
public :
//隐藏
void fun1( double i){
cout << "B::fun1() : " << i << endl;
}
};
int main(){
B b;
b.fun1(5); //调用B类中的函数
b.fun1(1, 2); //出错,因为基类函数被隐藏
system ( "pause" );
return 0;
}

3.重写:重写翻译自override,也翻译成覆盖(更好一点),是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。

实例代码如下:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<iostream>
using namespace std;
class A{
public :
virtual void fun3( int i){
cout << "A::fun3() : " << i << endl;
}
};
class B : public A{
public :
//重写
virtual void fun3( double i){
cout << "B::fun3() : " << i << endl;
}
};
int main(){
A a;
B b;
A * pa = &a;
pa->fun3(3);
pa = &b;
pa->fun3(5);
system ( "pause" );
return 0;
}

上面为虚函数实现多态的代码,不明白的先看虚函数实现多态的原理。

重载和重写的区别:

(1)范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类中

(2)参数区别:重写与被重写的函数参数列表一定相同,重载和被重载的函数参数列表一定不同

(3)virtual的区别:重写的基类必须要有virtual修饰,重载函数和被重载函数可以被virtual修饰,也可以没有

隐藏和重写,重载的区别:

(1)与重载范围不同:隐藏函数和被隐藏函数在不同类中

(2)参数的区别:隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定同;当参数不同时,无论基类中的函数是否被virtual修饰,基类函数都是被隐藏,而不是被重写

调试运行如下代码:

?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<iostream>
using namespace std;
class A{
public :
void fun1( int i, int j){
cout << "A::fun1() : " << i << " " << j << endl;
}
void fun2( int i){
cout << "A::fun2() : " << i << endl;
}
virtual void fun3( int i){
cout << "A::fun3(int) : " << i << endl;
}
};
class B : public A{
public :
//隐藏
void fun1( double i){
cout << "B::fun1() : " << i << endl;
}
//重写
void fun3( int i){
cout << "B::fun3(int) : " << i << endl;
}
//隐藏
void fun3( double i){
cout << "B::fun3(double) : " << i << endl;
}
};
int main(){
B b;
A * pa = &b;
B * pb = &b;
pa->fun3(3); //重写,多态性,调用B的函数
b.fun3(10); //隐藏,调用B的函数
pb->fun3(20); //隐藏,调用B的函数
system ( "pause" );
return 0;
}

输出结果为:

?

1
2
3
4
B::fun3( int ) : 3
B::fun3( int ) : 10
B::fun3( int ) : 20
请按任意键继续. . .

 

静态数据成员只能在全局区域进行初始化,而不能在类体中进行(构造函数中初始化也不行),且静态数据成员不涉及对象,因此不受类访问限定符的限制。

例子说明如下:

class CBook {
public:
    static double m_price;
};
double CBook::m_price = 8.8; // 只能在这初始化,不能在CBook的构造函数或直接初始化

 

C++中可以重载的运算符:new/delete、new[]/delete[]、++等。

不可以重载的运算符:、.、::、?:、sizeof、typeid、.、**、不能改变运算符的优先级。

 

重载++和--时是怎么区分前缀++和后缀++的?

例如当编译器看到++a(先自增)时,它就调用operator++(a);

但当编译器看到a++时,它就调用operator++(a, int)。即编译器通过调用不同的函数区别这两种形式。

 

C++的多态性分为静态多态和动态多态。

静态多态性:编译期间确定具体执行哪一项操作,主要是通过函数重载和运算符重载来实现的;

动态多态性:运行时确定具体执行哪一项操作,主要是通过虚函数来实现的。

 

虚函数原理考点,例如下面程序的输出是什么?

 
复制代码
class A {
public:
    virtual void funa();
    virtual void funb();
    void func();
    static void fund();
    static int si;
private:
    int i;
    char c;
};
复制代码

问:sizeof(A) = ?

解答:
关于类占用的内存空间,有以下几点需要注意:
(1)如果类中含有虚函数,则编译器需要为类构建虚函数表,类中需要存储一个指针指向这个虚函数表的首地址,注意不管有几个虚函数,都只建立一张表,所有的虚函数地址都存在这张表里,类中只需要一个指针指向虚函数表首地址即可。
(2)类中的静态成员是被类所有实例所共享的,它不计入sizeof计算的空间
(3)类中的普通函数或静态普通函数都存储在栈中,不计入sizeof计算的空间
(4)类成员采用字节对齐的方式分配空间
答案:12(32位系统)或16(64位系统)
 

虚继承的作用是什么?

 

在多继承中,子类可能同时拥有多个父类,如果这些父类还有相同的父类(祖先类),那么在子类中就会有多份祖先类。例如,类B和类C都继承与类A,如果类D派生于B和C,那么类D中就会有两份A。为了防止在多继承中子类存在重复的父类情况,可以在父类继承时使用虚函数,即在类B和类C继承类A时使用virtual关键字,例如:
class B : virtual public A
class C : virtual public A
注:因为多继承会带来很多复杂问题,因此要慎用。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FeelTouch Labs

一杯咖啡的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值