您的位置:首页 > 编程语言 > C语言/C++

C++面试问题总结 1

2016-11-17 16:25 190 查看
/* scanf / printf 的返回值是什么? */

int scanf (const char *restrict format, ...)

scanf函数返回成功读入的数据项数,读到文件末尾出错则返回EOF

int printf(const char *format, [argument], ...);

返回成功打印的字符数,若错误返回一个负值

/* __cdecl关键字是什么? */

__cdecl 是C语言默认的调用惯例

(栈底为高地址,栈顶为低地址)

参数传递:从右至左的顺序压参数入栈

出栈方:  函数调用方

名字修饰:直接在函数名称前加一个下划线

int fun (int n, float m);

1.将m压入栈    2.将n压入栈    

3.调用_fun,此步分两个步骤 (a.将返回地址压入栈(即调用)_fun之后的下一条指令的地址)  b.跳转到_fun执行)

__cdecl是调用者维护栈平衡,就是参数入栈了多少字节,就要弹出多少字节,还原调用栈之前的状态

/* 函数调度方式有几种? 分别描述 */

其他几种调用惯例:

      出栈方
 参数传递    

  stdcall      函数本身        从右至左顺序入栈    

  

  fastcall     函数本身        头两个DWORD类型     
             
        或占更少字节的参数
             
        被放入寄存器,其他
                       剩下的参数从右到左
             
        顺序入栈

  pascal       函数本身   从左至右顺序入栈

  

  特殊:

        nakedcall    

特点是编译器不产生任何保护寄存器的代码

thiscall
C++特有,专用于类成员函数的调用

/* 为什么参数要从右至左入栈? */

参数入栈顺序从右至左的好处就是可以动态变化参数个数

可变参数主要通过第一个定参确定参数列表,从右至左入栈后,函数调用时pop出第一个参数就是参数列表的第一个定参

(若从左至右入栈,最前面的参数被压在栈底,除非知道参数个数,否则无法通过栈指针的相对位移求得)

如果支持可变参的函数,参数入栈顺序几乎必然是自右向左入栈;并且参数出栈也不能由函数自己完成,而应该由调用者完成。

(因为函数自身不知道调用者传入了多少参数,需要调用者负责将所有参数出栈)

(自右向左规则下,最左边的定参后入栈,离函数调用点有确定的距离)

/*
------------------------------------------------------------------------------------------
*/

/* 宏定义都有哪些用法? */

宏定义常用方法

1. 简单文本替换

#define MAX 10

MAX在预编译时会替换成10,将特殊的值定义宏以增加代码的可读性,且方便修改

2.带参数的宏

#define max(a,b) ((a)>(b)? (a),(b))

类似于函数一样接受参数,需要注意对每个参数加上括号,防止替换后出现优先级错误

3.防止多个文件对一个头文件重复引用

#ifndef _STDIO_H_

#define _STDIO_H_

#endif

4.条件编译

可以在编译的同时设置编译环境,在跨平台和系统的开发中常用

#ifdef WINDOWS

...

#endif

#ifdef LINUX

...

#endif

5.define中的三个特殊符号:#,##,#@

 #define Conn(x,y) x##y // 表示x连接y

 int num = Con(123,456);   num = 123456

 char* str = Con("abc","def"); str = "abcdef" 

 #define ToChar(x) #@x // 字符化操作符,返回一个const char

 

 char ch = ToChar(1);  a = '1'

 (参数转换后若超出变量的大小,编译器会报错)

 #define ToString(x) #x // 字符串化操作符

 string str = ToString(123abc);  str = "123abc"

/* inline 关键字是什么? 它与宏定义有什么区别? */

inline 是C/C++中的一个关键字,用来定义一个类的内联函数
引入它的原因是为了替代表达式形式的宏定义

C中可定义带参数的宏,这种宏在形式与使用上类似于函数,但用预编译器实现,没有参数压栈、代码生成的过程,
效率很高;但宏定义不具有检查参数的功能,在使用上仅仅是符号的替换,存在一系列隐患

C++中引入了类与类的访问控制,当一个操作涉及到类的private/protected成员,就不能以宏定义实现(无法传入this指针)

inline 消除了宏定义的缺点,同时又继承了宏定义的优点:

1. inline 定义类的内联函数,函数的代码被放入符号表中。在使用时直接替换,没有调用的开销

2. 类的内联函数在调用时,会检查参数类型,消除了隐患

3. inline 作为类的成员函数,可以访问类的private/protected成员

在类中定义不需要inline修饰。编译器自动化为内联函数;在类外定义时需要在前面添加inline关键字

此外,inline 对于编译器来说只是一种可忽略的建议,比如一个长达1000行的函数指定成inline,
编译器就会忽略这个inline ,将这函数还原成普通函数。

一般来说,内联机制适用于体积小只有几行的函数,它节省了一些调用函数的开销,但会造成重复代码增多,函数的体积增大导致整个程序臃肿。一方面增加了内存的负担,另一方面代码的执行是将代码移动至缓存,cpu再执行缓存中的代码,因此代码的臃肿会导致缓存命中几率下降,缓存又会从主存加载指令,严重影响时间效率。

因此,对于短小的函数可以使用inline提高速率,但长了反而会拖慢效率,这时适用于普通函数。

内联函数应该在头文件中定义,因为内联函数的定义对编译器必须是可见的,以便编译器能够在调用点展开该函数的代码。

/* C++ const 定义的常变量与宏定义有什么区别? */

C++常变量与宏定义的区别

1. 编译器处理方式不同

define是在预处理阶段展开

const是编译运行阶段使用

2.类型和安全检查不同

define 没有类型  纯替换

const有具体的类型,在编译阶段会执行类型检查

3.存储方式不同

define仅仅是展开

const会分配内存

#define 没有作用域,如果没有#undef是一直有效的,可能会污染其他人写的变量;

而const是有作用域的

const 可以节省空间

编译器会对const定义的变量优化:当常变量被引用或取地址时,分配空间,避免访问内存的低效率(全局变量)

const double Pi = 3.14

double i = Pi // 此时为Pi分配内存

double j = Pi // 没有内存分配

/* 以下程序的输出是什么?为什么? */

#include <iostream>

using namespace std;

void main()

{

    const int a = 10;

    int b = 0;

    int *p = (int*)&a;

    *p = 100;

    b = a;

    cout<<"a = "<<a<<endl;

    cout<<"b = "<<b<<endl;

    cout<<"*p = "<<*p<<endl;

}

输出结果为:

a = 10

b = 10

*p = 100

a是一个常变量,它的值在编译期间已经确定,p指向a的地址,并将a所在地址的值修改成了100,所以p解引用后输出的值为100

而变量b赋值成a的时候,实际相当于 b = 10 ,当出现a这个符号的时候自动识别成10

/*
------------------------------------------------------------------------------------------
*/

/* 为什么C++里声明类要加分号? */

C++里不加分号,后面可以直接写变量名,就可以在声明类的同时,声明一个这个类的实例。

分号是告诉编译器,这个声明到此为止,没有后面的变量名了;

类是声明而不是定义所以不占空间,类似于函数声明需要加分号,而函数定义不需要。

/*
------------------------------------------------------------------------------------------
*/

/* 什么是函数重载?它是如何实现的? */

C++中函数重载的实现

1. 编译器解决命名冲突

编译器会将重载的函数根据函数的作用域、参数列表修改函数签名,从而解决命名冲突

class test{

public:

    void print(int i)

    {

        cout<<"int i"<<endl;

    }

    void print(char ch)

    {

        cout<<"char ch"<<endl;

    }

};

void print(int i);   -->  _ZN4test5printEi

void print(char ch); -->  _ZN4test5printEc

2. 重载函数调用匹配

重载函数定义完成后,按照规则判断匹配函数:

精确匹配:参数匹配不需要做转换;

提升匹配:即整数提升bool -> int , char -> int ... 以及float -> double;

标准转换匹配:如 int -> double 、double -> int;

void print(int);

void print(const char*);

void print(double);

void print(long);

void print(char);

void test(char c,int i,short s,float f)

{

    print(c); // 精确匹配,调用print(char)

    print(i); // 精确匹配,调用print(int)

    print(s); // 整数提升,调用print(int)

    print(f); // float提升double,调用print(double)

    print('a'); // 精确匹配,调用print(char)

    print(49); // 精确匹配,调用print(int)

    print(0); // 精确匹配,调用print(int)

    print("a"); // 精确匹配,调用print(const char*)

}

注意:定义太少或太多的重载函数,都会导致歧义性

void fun(char*);

void fun(int*);

fun(0);// 这里两个函数都可以匹配,编译器会报错
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++