C++笔试面试常考知识点汇总(二)

51:顶层const与底层const的区别?
用名词顶层const表示指针本身是个常量,而用名词底层const表示指针所指的对象是一个常量。
更一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用。
而底层const则与指针和引用等复合类型的基本类型部分有关。
当执行拷贝操作时,顶层const不受什么影响;对于底层const,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换,一般来说,非常量可以转换为常量,反之则不行。

52:什么是常量表达式?
常量表达式是指不会改变并且在编译过程就能得到计算结果的表达式。
新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

53:什么是数据抽象和封装?
类的基本思想是数据抽象和封装。数据抽象是一种依赖于接口和实现分离的编程技术。类的接口包括用户所能执行的操作;类的实现则包括类的数据成员、负责接口实现的函数体以及定义类所需的各种私有函数。
封装实现了类的接口和实现的分离。封装后的类隐藏了它的实现细节。也就是说,类的用户只能实现接口而无法访问实现部分。

54:友元函数及友元类?
类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为它的友元。
友元声明只能出现在类的内部。

55:explicit关键字的作用?
关键字explicit只能作用于只有一个实参的构造函数(需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为explicit的)。只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复。
当使用explicit关键字声明构造函数时,它将只能以直接初始化的形式使用。而且,编译器将不会在自动转换过程中使用该构造函数。

56:我们已经使用过操纵符endl,它完成换行并刷新缓冲区的工作。IO库还有两个类似的操纵符:flush和ends。flush刷新缓冲区,但不输出任何额外的字符;ends向缓冲区插入一个空字符,然后刷新缓冲区。

57:静态内存和动态内存的区别?
(1)静态内存编译时完成分配,不占用CPU资源;动态内存运行时完成分配,分配与释放都占用CPU资源
(2)静态内存在栈上分配;动态内存在堆上分配
(3)静态内存按计划分配,由编译器负责;动态内存按需分配,由程序员负责
(4)动态内存分配需要指针和引用支持,静态内存不需要

58:智能指针有哪几种?
shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指向的对象。标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。

59:什么时候会递增递减shared_ptr的引用计数?
对于shared_ptr:赋值,拷贝,向函数传递一个智能指针,或函数返回一个智能指针都会增加当前智能指针的计数;向一个shared_ptr赋予一个新值或者一个shared_ptr被销毁时,计数器就会递减。

60:默认构造函数?
默认构造函数(default constructor)就是在没有显式提供初始化式时调用的构造函数。它由不带参数的构造函数,或者为所有的形参提供默认实参的构造函数定义。
如果你没有为你的类提供任何构造函数,那么编译器将自动为你生成一个默认的无参构造函数。一旦你为你的类定义了构造函数,哪怕只是一个,那么编译器将不再生成默认的构造函数。
有多种原因,你需要为你的类提供默认构造函数(若没有定义其他构造函数,则由编译器提供默认构造函数,否则,就不存在默认构造函数,遇到下面问题时将编译失败):
1) 当你使用静态分配的数组,而数组元素类型是某个类的对象时,就要调用默认的构造函数。
2) 当你使用动态分配的数组,而数组元素类型是某个类的对象时,就要调用默认的构造函数,因为new操作符要调用Object类的无参构造函数类初始化每个数组元素。
3) 当你使用标准库的容器时,如果容器内的元素类型是某个类的对象时,那么这个类就需要默认的构造函数,原因同上。
4) 一个类A以另外某个类B的对象为成员时,如果A提供了无参构造函数,而B未提供,那么A则无法使用自己的无参构造函数。

class B
{
    B(int i){}
};
class A
{
    A(){}
    B b;
};
int main(void) 
{ 
    A a(); // error C2512: 'B' : no appropriate default constructor available
    return 0 ; 
}

5) 类A定义了拷贝构造函数,而没有提供默认的构造函数,B继承自A,所以B在初始化时要调用A的构造函数来初始化A,而A没有默认的构造函数,故产生编译错误。

class A
{
    A(const A&){}
};
class B : public A
{

};
int main(void) 
{ 
    B b; //error C2512:'B': no appropriate default constructor available
    return 0 ; 
}

可由以上得出结论,最好显示定义默认构造函数。

61:STL算法中的copy算法
原型:copy(vec.begin(),vec.end(),dest.begin());dest容器的空间要大于等于要拷贝进去的数据的个数。另有copy_n(vec.begin(),n,dest.begin());move(vec.begin(),vec.end(),dest.begin())算法与此类似。
其实对于该类问题,有统一的说法:那些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。而向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素(必须要自身保证这一点,编译器不会做检查,但是会运行错误,另有方法可利用插入迭代器)。

62:什么时候使用拷贝构造函数?
使用拷贝构造函数(以下5种形式)
一个对象显式或隐式初始化另一个同类型的对象。
函数的非引用传参。
函数非引用返回一个对象。
初始化顺序容器中的元素。
根据元素初始化列表初始化数组元素。

63:类模板的优点?
(1)可用来创建动态增长和减小的数据结构 (2)它是类型无关的,因此具有很高的可复用性。 (3)它在编译时而不是运行时检查数据类型,保证了类型安全 (4)它是平台无关的,可移植性 (5)可用于基本数据类型

64:假定CSomething是一个类,执行下面这些语句之后,内存里创建了__个CSomething对象?

CSomething a();// 没有创建对象,这里不是使用默认构造函数,而是定义了一个函数
CSomething b(2);//使用一个参数的构造函数,创建了一个对象。
CSomething c[3];//使用无参构造函数,创建了3个对象。
CSomething &ra=b;//ra引用b,没有创建新对象。
CSomething d=b;//使用拷贝构造函数,创建了一个新的对象d。
CSomething *pA = c;//创建指针,指向对象c,没有构造新对象。
CSomething *p = new CSomething(4);//新建一个对象。

65:牢记:字符串常量不能重新赋值。

66:对于一个运算符函数来说,它或者是类的成员,或者至少包含一个类类型的参数;重载运算符必须和用户定义的自定义类型的对象一起使用。

67:如果用位方法求x%y,则其通用公式为x&(y-1)

68:void *memset(void *s, int ch, size_t n);
函数解释:将s中前n个字节用ch替换并返回s。
作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法,通常为新申请的内存做初始化工作。

69:int fseek(FILE *stream, long offset, int fromwhere);函数设置文件指针stream的位置。
如果执行成功,stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置,函数返回一个非0值。
70:

class A
{
public:
    void print()
    {
        cout << "A:print()";
    }
};
class B: private A
{
public:
    void print()
    {
        cout << "B:print()";
    }
};
class C: public B
{
public:
    void print()
    {
A:: print();
    }
};
int main()
{
    C b;
    b.print();
}

对于该程序,有以下理解,B私有继承自A,则A中print对于B来说是私有的,对于C来说是不可访问的。

71:如果定义了拷贝构造函数,也必须定义默认构造函数。

72:为什么拷贝构造函数的类对象的形参必须是引用类型?
若不是引用类型:为了调用拷贝构造函数,必须拷贝它的实参,为了拷贝它的实参,我们又需要调用拷贝构造函数,如此无限循环。则我们的调用永远不会成功。

73:合成的拷贝构造函数
没有定义拷贝构造函数,编译器就会为我们合成一个。与合成的默认构造函数不同,即使我们定义了其他构造函数,也会合成拷贝构造函数。合成拷贝构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本(浅拷贝)。编译器将现在对象的每个非static成员,依次复制到正创建的对象。合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。
由此可见,当使用合成的拷贝构造函数时,由于浅复制的问题,容易出现错误。

74:编写拷贝赋值运算符时应该注意的问题?
1:形参列表应为const &类型:常量确保在函数内不改变传入实例的状态,引用避免调用拷贝构造函数,以提高效率
2:返回应该为引用类型,以方便连续赋值
3:判断传入的参数和当前实例是否是同一个实例,以避免出现删除自身内存的情况
4:删除实例自身已有的内存,避免出现内存泄露
更高级的方法是:创建一个临时实例,作为传入实例的副本,然后将当前实例数据成员和临时实例数据成员进行交换。当程序运行出临时实例的创建范围后,程序会自动调用析构函数析构临时实例。
具体可见本博客中String类的拷贝赋值运算符函数。

75:可通过将拷贝控制成员定义为=default来显示要求编译器为我们合成拷贝控制成员。可以通过在拷贝构造函数和拷贝赋值运算符后面加=delete 来阻止拷贝。

76:当一个重载运算符是成员函数时,this绑定到左侧运算对象。成员运算符函数的(显式)参数数量比运算对象的数量少一个。

77:重载递增++递减–运算符。
首先,每种运算符建议重载前置和后置两个版本。
其次,前置版本返回引用,后置版本返回值。
为解决前置和后置运算符无法区分的问题,后置版本接收一个额外的(不被使用)int类型的形参。当我们使用后置运算符时,编译器为这个形参提供一个值为0的实参。这个形参的唯一作用就是区分前置版本和后置版本的函数,而不是真的要在实现后置版本时参与运算。后置递增递减运算符调用各自的前置版本来完成实际工作。

//前置运算符,返回引用
A &operator++(){v++;return *this}
A &operator--(){v--;return *this}
//后置运算符,返回值
A operator++ (int x){int temp=v;v++;return tmp;}
A operator-- (int x){int temp=v;v--;return tmp;}

78:在继承体系中,首先初始化基类的的部分,然后按照声明顺序依次初始化派生类成员。

79:在类名(函数名)后跟一个关键字final,可以阻止这个类成为基类(被覆盖)。

80:表达式的静态类型在编译时总是已知的,它是变量声明时的类型或表达式生成的类型;动态类型则是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才知道。

81:因为一个基类的对象可能是派生类对象的一部分,也可能不是,所以不存在从基类向派生类的自动类型(隐式类型)转换。

82:若通过基类的引用或指针调用派生类虚函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本。即默认实参是静态绑定,而虚函数确是动态绑定。

83:可以利用作用域运算符来回避虚函数机制。

84:int i=-2147483648;则~i,-i,1-i,-1-i分别为:
计算机中以补码表示数据和运算:
-2147483648(-2^31)的二进制表示为:1000 0000 0000 0000 0000 0000 0000 0000
则~i=2^31-1=2147483647
对一个数求负,相当于对其求补运算,即仍为1000 0000 0000 0000 0000 0000 0000 0000,也可以这样理解(-2^31+2^32=2^31,2^31不能由正数表示,还需用负数表示,为-2^31)
1-i相当与-i+1,即拿-i的补码和1相加,为-2147483647
-1-i即拿-1的补码和-i的补码相加:-1的补码为1111 1111 1111 1111 1111 1111 1111 1111,相加得0111 1111 1111 1111 1111 1111 1111 1111为2^31-1=2147483647

85:

    char dog[]="wang\0miao";
    sizeof(dog);//结果为10:wang(4)+\0(1)+miao(4)+1
    strlen(dog);//结果为4

86:vector是顺序存储的,只有在尾部插入(删除)才不会导致迭代器失效,在头部或者中间插入(删除)都会导致插入(删除)部位及其后的所有迭代器失效。
map是映射,key和value是一一对应的,在内存中是零散存在的,迭代器通过key找到value,无论怎么插入都不会让迭代器失效,删除只会使得被删除的迭代器失效。

87:在C++中,如果一个整形变量频繁使用,最好将其定义为register类型(寄存器类型)

88:

    int n[][3]={10,20,30,40,50,60};
    int (*p)[3];
    p=n;
    cout<<p[0][0]<<','<<*(p[0]+1)<<','<<(*p)[2]<<endl;
    //输出10,20,30

n为一个2*3的数组,p=n,则p[0][0]=n[0][0],p[0]+1=n[0]+1=n[0]1,*p=n[0],(*p)[2]=n[0][2](也可以这样理解,p指向一个数组,该数组的第三个元素为30)

89:若myclass为一个类,执行myclass a[4],*p[5]语句时会自动调用该类构造函数4次,因为没有初始化的指针为空,不指向任何对象,也不调用构造函数。

90:二叉树的度:
1)度:结点拥有的子树数称为结点的度。度为0的结点称为叶结点或终端结点。
2)分支线总数:从分支线进入结点的角度计算分支线总数,对于二叉树除了根结点以外每个结点都有一个分支线,因此分支线总数为n-1,其中n为结点的总数。
若用n 1表示度为1的结点数,那么二叉树结点总数n=n 0+n 1+n 2(此式比较好理解,二叉树只有度为0,1,2的结点)
而分支线总数为n-1=n 1 +2* n 2
两式可推出 n 0 + n 1 + n 2-1=n 1 +2* n 2,即n 2 =n 0 -1,即度为2的节点个数比度为0的节点个数少一个。
当知道二叉树度为1的节点和度为2的节点,由此式可以得出二叉树的节点数。

91:完全二叉树:只有最下面的两层结点度能够小于2,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。
满二叉树:除了叶结点外每一个结点都有左右子结点且叶结点都处在最底层的二叉树。

92:AVL树(平衡二叉树):它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

93:

void getmemory(char*p)  
{    
    p=(char *) malloc(100);    
    strcpy(p,"hello world");  
}
int main( ) 
{    
    char *str=NULL;    
    getmemory(str);    
    printf("%s\n",str); //输出null 
    free(str);//当str为空时,free不会做任何操作,而非动态申请内存,会出错    
    return 0;    
}

94:

    char s1[]="12345",*s2="1234";
    printf("%d\n" ,strlen(strcpy(s1,s2)));//输出4,将s2中的'\0'也拷贝过去了

95:

class Test{
public:
    int a;
    int b;
    virtual void fun() {}
    Test(int temp1 = 0, int temp2 = 0)
    {
        a=temp1 ;
        b=temp2 ;
    }
    int getA()
    {
        return a;
    }
    int getB()
    {
        return b;
    }
};

int main()
{
    Test obj(5, 10);
    // Changing a and b
    int* pInt = (int*)&obj;
    *(pInt+0) = 100;  
    *(pInt+1) = 200;  
    cout << "a = " << obj.getA() << endl;
    cout << "b = " << obj.getB() << endl;
    return 0;
}

输出结果为a=200,b=10.需要考虑到虚函数表位于对象地址首部,然后接下来才是a和b。

96:执行”int x=1;int y=~x;”语句后,y的值为?
假设int占1个字节,那么1的二进制表示是 0000 0001 ,~表示按位取反,则 0000 0001变为 1111 1110,在计算机中整数用补码形式表示,正数的补码是它本身,负数的补码是原数值除符号位按位取反再加一,由补码求原数值也是按位取反再加一,那么 1111 1110 除符号位按位取反再加一变成 1000 0010,即 -2。

97:类中的this指针的含义?
this指针是一个隐含的指针,它是指向对象本身的,表示当前对象的地址。
在一个非静态的成员里面,this关键字就是一个指针,指向此次调用该函数的那个对象。在类a的非const成员函数里,this的类型是a*,但是this不是一个常规变量,所以不可以获取this的地址或者给它赋值。在类a的const成员函数里,this的类型是const a*,不可以对这个对象本身进行修改。

98:volatile关键字
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。简单地说,就是防止编译器对代码的优化。

99:对于switch语句,当同时出现多个case时,其正确的使用方法是:

    case a:
    case e:
    case i:
    case o:
    case u:
        cnt++;//统计元音字符个数
        break;

100:对struct进行sizeof操作
对于一个struct取sizeof,要考虑对界的问题。对界是取struct中最大的数据作为对界值。对于以下三个struct,有以下解答

struct s1
{
  char a;
  double b;
  int c;
  char d; 
};
//sizeof(struct s1)=1+7+8+4+1+3=24;对界值取8(7,3是为满足对界填充的空间)
struct s2
{
  char a;
  char b;
  int c;
  double d;  
};
//sizeof(struct s2)=1+1+2+4+8=16;对界值取8(2是为满足对界填充的空间)
struct X 
{ 
    short s; 
    int i; 
    char c;
};
//sizeof(struct X)=2+2+4+1+3=12;对界值取4(第二个2和3是为满足对界填充的空间)
  • 5
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值