c/c++ 面试笔试知识点----牛客网(5)

来源:互联网 发布:十秒竞拍源码 编辑:程序博客网 时间:2024/05/21 21:49
1
struct成员类型不可以是它自己。
因为会递归定义。理论上这样导致结构体的大小不能被计算(无限大小)。所以不能在结构体里的成员类型是结构体本身。但是成员可以定义为该结构体的指针。就像你上面这段代码。因为指针的大小是已知的(随编译器和操作系统而定)。所以可以定义为该结构体的指针,但不是该结构体。
2
成员函数操作两个对象时,只需传递一个对象参数,另一个是调用成员的的this。友员函数则需要两个对象参数
3
这里的联编就是常说的绑定吧,C++默认静态绑定,当需要解释为动态绑定时的办法就是将基类中的函数声明成虚函数。然而即使是调用虚函数,也只有在使用基类指针或引用的时候才动态绑定,其它应该还是静态绑定的,并且基类的构造函数中对虚函数的调用不进行动态绑定, 题目中没有说明对象是如何访问的,是否用到了指针或引用,个人认为题目的描述不够严谨。
4
动态库的函数到实际使用的时候才加载到内存,所以运行速度较慢
5
因为对于非虚成员函数,C++这门语言是静态绑定的。这也是C++语言和其它语言Java, Python的一个显著区别。以此下面的语句为例:
pA->test();
这语句的意图是:调用对象 pA 的 test 成员函数。如果这句话在Java或Python等动态绑定的语言之中,编译器生成的代码大概是:
找到 pA 的 test 成员函数,调用它。(注意,这里的找到是程序运行的时候才找的,这也是所谓动态绑定的含义:运行时才绑定这个函数名与其对应的实际代码。有些地方也称这种机制为迟绑定,晚绑定。)
但是对于C++。为了保证程序的运行时效率,C++的设计者认为凡是编译时能确定的事情,就不要拖到运行时再查找了。所以C++的编译器看到这句话会这么干:
1:查找 pA 的类型,发现它有一个非虚的成员函数叫 test 。(编译器干的)
2:找到了,在这里生成一个函数调用,直接调A:: test ( pA )。
所以到了运行时,由于 test ()函数里面并没有任何需要解引用 pA 指针的代码,所以真实情况下也不会引发segment fault。这里对成员函数的解析,和查找其对应的代码的工作都是在编译阶段完成而非运行时完成的,这就是所谓的静态绑定,也叫早绑定。
正确理解C++的静态绑定可以理解一些特殊情况下C++的行为。
6
volatile:易变的,不稳定的。volatile用来声明那些可能在你的程序本身不知道的情况下会发生改变的变量。
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
对于一般变量:为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中。以后再取变量值时,就直接从寄存器中取值。
一个参数既可以是const也可以是volatile:一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。(简单点就是该程序代码不能试图去修改它,但不排除硬件方面修改了它,我们每次都得重新读取它的值。)
几个应用例子:
a. 并行设备的硬件寄存器(如:状态寄存器)。
b. 一个中断服务子程序中会访问到的非自动变量。(个人理解:中断服务子程序不能用缓存在寄存器中的值来判断事件,因为那个值可能被修改了,需要重新读取。所以一般需要把这种变量声明为volatile。)
c. 多线程应用中被几个任务共享的变量。
7
在Windows下,最好的方式是用VirtualAlloc分配内存。不是在堆,也不是在栈,而是直接在进程的地址空间中保留一块内存,虽然用起来最不方便,但是速度最快,也是最灵活的。
8
同时定义前缀式操作符和后缀式操作符存在一个问题:它们的形参数目和类型相同,普通重载不能区别所定义的前缀式操作符还是后缀式操作符。为了解决这一问题,后缀式操作符函数接受一个额外的(即,无用的)int型形参。使用后缀式操作符进,编译器提供 0 作为这个形参的实参。尽管我们的前缀式操作符函数可以使用这个额外的形参,但通常不应该这样做。那个形参不是后缀式操作符的正常工作所需要的,它的唯一目的是使后缀函数与前缀函数区别开来。
9
A: 预处理是 C 语言程序从源代码变成可执行程序的第一步,主要是 C 语言编译器对各种预处理命令进行处理,包括头文件的包含、宏定义的扩展、条件编译的选择等。
B: 编译之前,C 语言编译器会进行词法分析、语法分析(-fsyntax-only) ,接着会把源代码翻译成中间语言,即汇编语言 编译程序工作时,先分析,后综合,从而得到目标程序。所谓分析,是指词法分析和语法分析;所谓综合是指代码优化,存储分配和代码生成。 值得一提的是,大多数的编译程序直接产生机器语言的目标代码,形成可执行的目标文件,但也有的编译程序则先产生汇编语言一级的符号代码文件,然后再调用汇编程序进行翻译加工处理,最后产生可执行的机器语言目标文件。 
C: 链接是处理可重定位文件,把它们的各种符号引用和符号定义转换为可执行文件中的合适信息( 一般是虚拟内存地址 ) 的过程。
10
模板的使用实际上是将类模板实例化成一个类
11
int (*fun)(int *)是函数指针,指向函数的指针变量,即本质是一个指针变量。 int* fun(int *) 指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针
12
非常量引用必须是左值
左值和右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象。
在标准C++语言中,临时量(术语为右值,因其出现在赋值表达式的右边)可以被传给函数,但只能被接受为const &类型。
函数形式参数是临时量,是右值。所以只能被接受为const &类型 。故A错。
13
parallel 的中文解释是 “并行”..
14
当派生类中不含对象成员时
· 在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;
· 在撤消派生类对象时,析构函数的执行顺序是:派生类的构造函数→基类的构造函数。
当派生类中含有对象成员时
· 在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;
· 在撤消派生类对象时,析构函数的执行顺序:派生类的构造函数→对象成员的构造函数→基类的构造函数。
15
知识点:
函数指针变量:
函数指针变量的声明方法为:
返回值类型 ( * 指针变量名) ([形参列表]);
根据定义,
int(*pf)(float);
int (*p)(float)=&f1;
pf,p都是函数指针变量。
函数地址
C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。
函数地址的获取,可以是函数名,也可以在函数名前加取地址符&
16
volatile提醒编译器它后面所定义的变量随时都有可能改变 ,因此编译后的程序每次需要存储或读取这个变量的时候 ,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储, 可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
17
++ 作为后缀时的优先级比* 高,结合律从左到右。因此D *p++ 是先p++, 而后*(p++)的。
++作为后缀时优先级和()一样高,结合律从左到右。结合律仅考虑同优先级时。因此(*p)++ 能够先得到1009 然后++。
++ 作为前缀时的优先级和* 一样高, 结合律从右到左, 所以C可以写成++*p == ++(*p)的。
18
c1要调用一次构造函数;
c2只是一个指针,用来动态描述对象,不会调用类的构造函数;
c3的右边新创建一个对象会调用构造函数。但是注意,这里的赋值运算符不是类中的赋值运算符,而是普通的赋值运算符;
c4是一个对象引用,是c1的一个别名,所以不会调用构造函数。
19
对于类A,  是建立在堆上的对象指针pa, 手动释放
对于类B, 是建立在栈上的对象b,main函数结束就释放
对类C ,在静态存储区创建了一个对象c ,程序结束时候释放
对类D,也是在静态存储区建立对象d,但是局部变量,程序结束时候释放.
析构函数调用顺序: 
先调用A的析构,因为delete pa .  A
再释放栈上的对象b,             B
关键看CD的顺序.
c是全局对象,对它的初始化是在main函数之前,所以析构时候要放在最后.
也就是先析构d ,然后再析构c
20
在类的成员函数中能不能调用delete this?答案是肯定的,能调用,而且很多老一点的库都有这种代码。假设这个成员函数名字叫release,而delete this就在这个release方法中被调用,那么这个对象在调用release方法后,还能进行其他操作,如调用该对象的其他方法么?答案仍然是肯定 的,调用release之后还能调用其他的方法,但是有个前提:被调用的方法不涉及这个对象的数据成员和虚函数。说到这里,相信大家都能明白为什么会这样 了。
根本原因在于delete操作符的功能和类对象的内存模型。当一个类对象声明时,系统会为其分配内存空间。在类对象的内存空间中,只有数据成员和虚函数表指针,并不包含代码内容,类的成员函数单独放在代码段中。在调用成员函数时,隐含传递一个this指针,让成员函数知道当前是哪个对象在调用它。当 调用delete this时,类对象的内存空间被释放。在delete this之后进行的其他任何函数调用,只要不涉及到this指针的内容,都能够正常运行。一旦涉及到this指针,如操作数据成员,调用虚函数等,就会出现不可预期的问题。
为什么是不可预期的问题?delete this之后不是释放了类对象的内存空间了么,那么这段内存应该已经还给系统,不再属于这个进程。照这个逻辑来看,应该发生指针错误,无访问权限之类的令系统崩溃的问题才对啊?这个问题牵涉到操作系统的内存管理策略。delete this释放了类对象的内存空间,但是内存空间却并不是马上被回收到系统中,可能是缓冲或者其他什么原因,导致这段内存空间暂时并没有被系统收回。此时这段内存是可以访问的,你可以加上100,加上200,但是其中的值却是不确定的。当你获取数据成员,可能得到的是一串很长的未初始化的随机数;访问虚函数表,指针无效的可能性非常高,造成系统崩溃。
大致明白在成员函数中调用delete this会发生什么之后,再来看看另一个问题,如果在类的析构函数中调用delete this,会发生什么?实验告诉我们,会导致堆栈溢出。原因很简单,delete的本质是“为将被释放的内存调用一个或多个析构函数,然后,释放内存” (来自effective c++)。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。
21
A是定义一个新变量,把x赋值给它    int rx = x;
B是定义一个int变量,把x的地址赋给它   int rx = &x;
C是定义一个int指针变量,把x的地址赋给它 int *rx = &x;
D是定义一个引用变量,即把rx定义为x的别名    int &rx = x;
22
fscanf()作用是从文件流中读取数据,fprintf()是将数据写入到文件。不要搞混。
格式:int fscanf(FILE*stream,constchar*format,[argument...]);
参数:%d:读入一个十进制整数.
%s : 读入一个字符串,遇空字符‘\0'结束。
%c : 读入一个字符。无法读入空值。空格可以被读入。
23
多线程调用时要进行保护时,主要是针对全局变量和静态变量的,函数内的局部变量不会受到影响。
24
A,正确,分编译时多态和运行时多态
B,编译时多态可以通过函数重载实现,具体表现在根据参数的个数和类型不同选择合适的同名函数
C,运行时多态通过虚函数实现,就是运行时根据对象类型自动选择正确的调用接口。模板属于编译时多态性,因为编译时自动根据模板生成模板函数。
D,运行时多态是根据对象类型自动选择正确的调用函数,也叫动态绑定。
25
1) malloc 函数: void *malloc(unsigned int size)
     在内存的动态分配区域中分配一个长度为size的连续空间,如果分配成功,则返回所分配内存空间的首地址,否则返回NULL,申请的内存不会进行初始化。
2)calloc 函数: void *calloc(unsigned int num, unsigned int size)
     按照所给的数据个数和数据类型所占字节数,分配一个 num * size 连续的空间。
    calloc申请内存空间后,会自动初始化内存空间为 0,但是malloc不会进行初始化,其内存空间存储的是一些随机数据。
3)realloc 函数: void *realloc(void *ptr, unsigned int size)
    动态分配一个长度为size的内存空间,并把内存空间的首地址赋值给ptr,把ptr内存空间调整为size。
    申请的内存空间不会进行初始化。
4)new是动态分配内存的运算符,自动计算需要分配的空间,在分配类类型的内存空间时,同时调用类的构造函数,对内存空间进行初始化,即完成类的初始化工作。动态分配内置类型是否自动初始化取决于变量定义的位置,在函数体外定义的变量都初始化为0,在函数体内定义的内置类型变量都不进行初始化。
阅读全文
0 0
原创粉丝点击