c++面试 笔试基础知识学习记录

1. int (*p)[4] 和 int* p[4]

int (*p)[4] ,p是一个指针变量,指向一个存放4个int变量的一维数组,p+1是向后移动数组长度个字节大小,也就是向后移动4个int字节的大小。

*(*(p+1)+2)=*(p[1]+2)

int *p[4] ,等价于int *(p[4]),[]优先级高于*所以p首先是一个数组。即定义了一个指针数组,p就是指针数组名,数组中存放了4个指向int变量的指针。和普通数组类似,只不过数组里存的是指针。

2.字符型指针数组

char* data[]={"1234","456","123145"}

上述语句中的元素“1234” “456”等都是char*类型的

所以data数组中存储了三个元素,元素类型为char*, data[0]就是“1234”,data[1]就是“456”.....

*data就是data[0],*(data+1)就是data[1]

为什么char*类型可以是“1234”这样的一个字符串变量呢,因为字符串变量的本质表现是其第一个字符的地址,char*变量中存储的就是该字符串首地址,结尾标志是\0。

 

函数调用时的入栈方式:首先是函数调用的下一条指令的地址入栈,然后函数参数入栈(顺序一般是从右向左依次入栈),接下来是局部变量入栈,调用函数运行完后,依次出栈,栈顶指针指向之前存的下一条指令的地址,程序从该位置继续运行。

关于char* p="abc" 和char p[]="abc"的区别,前者不能改变和重新赋值,后者可以改变其中的字符。具体如下:

指针常量和常量指针

指向常量的指针,就是常量指针,常量是不可以修改的。char buf[]="abcd",const char *p=buf,const修饰char。但是p可以重新指向其他的常量,方法:char buf2[]="1234",p=buf2;(这里隐式的把buf2转成了 const char*)

指针常量,指针本身是常量,不能改变其指向 。char buf[]="abcd",char* const p=buf,const修饰的指针p,指针p的内存是不可以修改的,所以使用这种方式必须初始化。p不可以指向其他的变量,但是p指向的变量的内容可以修改,例如:p[0]='m',此时p指向的变量内容为mbcd。

 

大端和小端模式

小端模式就是高位字节存放在内存的高地址端,低位字节存放在内存的低地址端

大端模式就是高位字节存放在内存的低地址端,低位字节存放在内存的高地址端

例如:

0x12 3f 78 34使用小端模式存放为:0x34 0x78 0x3f 0x12,顺序是从低地址到高地址

0x12 3f 78 34使用大端模式存放为:0x12 0x3f 0x78 0x34,顺序是从低地址到高地址

 

函数重载和函数重写(总是没理清)

函数重载 overload 同名不同参数。当两个函数具有相同的名称,但是参数列表(包括参数个数和参数类型)不同时,互相被成为重载函数。

  函数重载要求: 函数名必须相同,参数必须不相同(可以是类型不同也可以是参数个数不同,或者参数类型的顺序不同),返回值无特定要求。

函数重写 overwrite 指的是子类重新定义父类中具有相同名称和参数的虚函数,主要在继承关系中出现。

  函数重写要求:1.重写的函数和被重写的函数必须是虚函数,且分别位于基类的派生类中

         2.重写的函数和被重写的函数函数名和函数参数必须完全一致

         3.二者的返回值必须相同,或者返回指针或引用,当返回指针或引用时,派生类返回的指针或引用的类型要与基类返回的相同,或者是基类函数中返回的指针或                                     引用的派生类型。

函数重载的应用:

在同一作用域内,如果多个函数实现相同功能,但具有不同的参数列表,就得想很多个函数名,麻烦且使得代码不清晰,函数重载的应用就很好地提高了代码的可读性,不需要为同一个功能的函数起很多个名字。在调用函数的时候,编译器根据参数列表自动调用合适的对应的函数。

注意某个类型和他的引用类型不能重载,例如下面的例子,原因是调用时会出现二义性

例:
void f(int );
void f(int &);

关于const能不能实现重载的问题,可以参考这篇文章

 

公有继承和私有继承

首先,对于一个类,在类的外部,不允许对象直接访问私有成员和保护成员。

公有继承:对基类的公有成员和保护成员的访问属性不变;

  派生类的新增成员(新增加的成员)可以访问基类的公有成员和保护成员,但是不能访问基类的私有成员,也就是在类的内部,派生类可以访问类的公有成员和保护成员;

  在类的外部,也就是类的派生对象只能访问派生类的公有成员(包括继承的公有成员),访问不了私有成员和保护成员。

保护继承:基类中公有成员和保护成员被派生类继承后变为保护成员,私有成员被继承后仍是私有成员;

  派生类的新增成员可以访问基类的公有成员和保护成员,但是不能访问基类的私有成员;

  在类的外部,也就是类的派生对象只能访问派生类的公有成员(包括继承的公有成员),访问不了私有成员和保护成员。

私有继承:基类中公有成员和保护成员被派生类继承后变为私有成员;

  派生类的新增成员可以访问基类的公有成员和保护成员,但是不能访问基类的私有成员;

  在类的外部,也就是类的派生对象只能访问派生类的公有成员(包括继承的公有成员),访问不了私有成员和保护成员。

无论哪种继承。基类中的私有成员在派生类中都是不能访问的。

 

友元

在面向对象编程中,类的友元被授予访问当前类的非公有成员的权限。友元关系可以在类的定义中声明,它允许指定的类或函数成为当前类的友元,从而可以访问当前类的私有成员或受保护成员。

友元类的作用:如果把类A作为类B的友元,也就是A类是B类的友元,那么在A类的成员函数可以直接访问B类的私有成员,即友元类可以直接访问对应类的所有成员。

例如下面的代码:

class B{
    friend class A; //将A声明为B的友元,A类的成员函数能够直接访问B类对象的所有非公有成员
private:
    int data_b;
};

class A{
public:
    void print(B& obj){
        printf("%d",obj.data_b);
    }
};

 

构造和析构:

析构函数的返回类型是void,或者说析构函数无返回类型。析构函数可以且常常是虚函数。

派生类在创建对象时按照对基类的继承顺序先调用基类的构造函数再调用派生类的构造函数。

而派生类析构时,先对新增的一般成员进行析构,再对新增的成员对象进行析构,最后按照其继承的基类的相反顺序来调用基类的析构函数。

在继承关系中,析构函数要声明为虚函数,否则当使用基类的指针释放派生对象时,派生对象只会释放基类的部分,从而造成内存泄露。

 

类型转换static_cast dynamic_cast const_cast和reinterpret_cast

static_cast在编译期间转换,只能用于良性转换,例如:short->int,void指针和具体类型指针之间的转换,不能在两个具体类型的指针之间进行转换

const_cast用来去掉表达式的const或volatile修饰

dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。向上转型是无条件的,不会进行任何检测,所以都能成功;向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。它是动态的,是在程序运行期间转换的,要求基类必须包含虚函数

 

一道题:

int main()

{
    int arr[]={1,2,3,4,5,6,7};
    int *p=arr;
    *(p++)+=89;
    printf("%d,%d\n",*p,*(++p));
    return 0;
}
执行以上代码的输出:结果应该为3,3。
因为printf是从右向左压栈,也就是说从右往左的计算。

 

一道题:

class base1{

    privateint a,b;
    public:
    base1 ( int i ) : b(i+1),a(b){}
    base1():b(0),a(b){}
    int get_a(){return a;}
    int get_b(){return b;}
};
int main()
{
    base1 obj1(11);
    cout<<obj1.get_a()<<endl<<obj1.get_b()<<endl;
    return 0;
}

执行后的输出为:随机值  12。原因:类中成员的初始化顺序是它们在类中出现的顺序,也就是先初始化a再初始化b。而不是按照初始化列表中先b后a的顺序。

 

一道题:

python是用C语言写成的,根据名字空间特性,以下代码经过python编译器编译后,一共得到(2)个PyCodeObject对象。

1
2
3
4
5
6
class A:
    pass
def Fun():
    pass
a = A()
Fun()

解析:Python编译器在对Python源码进行编译的时候,对代码中的一个Code Block,会创建一个PyCodeObject对象与这段代码对应。
Python中确定Code Block的规则:当进入一个新的名字空间或作用域时,就算进入了一个新的Code Block了。

在Python中,类、函数和module都对应着一个独立的名字空间,因此都会对应一个PyCodeObject对象

一道题:

用1*3的瓷砖密铺3*20的地板有几种方式?

动态规划:dp[m]=dp[m-1]+dp[m-3].  原因,当前列的前一列要么横着放三块,占据了三列,这样就有dp[m-3],要么竖着放一列,共有dp[m-1],因此动态规划的状态转移方程为dp[m]=dp[m-1]+dp[m-3]。

一道题:

下列选项中,不可能是快速排序第2趟排序结果的是 ()。

解答该题的方法为:每经过一次快速排序,轴点元素必然就位,也就是经过一次排序必然有一个元素在其最终位置,经过两趟排序后必然有两个元素在其最终位置,因此只要检查是否满足有至少两个元素在其最终位置即可。

 

 

 

个人学习记录,如有错误请指出,感谢!

posted @ 2023-05-31 15:20  阳光中的影子  阅读(17)  评论(0编辑  收藏  举报