C++面试问题总结 1

/* 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);// 这里两个函数都可以匹配,编译器会报错




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值