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);// 这里两个函数都可以匹配,编译器会报错
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++面试问题总结1
- 常见C++面试问题总结2
- 面试总结5--C++基础问题Part1
- 常见C++面试问题总结1
- 面试常见C++问题总结
- 面试总结6--C++基础问题Part2
- C++面试中常被问的sizeof问题总结
- C++面试问题总结 2
- c和c++面试常见问题总结
- C++面试中关于sizeof问题总结
- 2016年校招秋招 C++开发 面试问题总结(中兴、CVTE、瑞晟、华为、YY)
- C++面试常见问题总结
- C++开发 面试问题总结(中兴、CVTE、瑞晟、华为、YY)
- 常见C++面试问题总结2
- C++面试问题总结 3
- C/C++面试常见问题总结
- 关于c++面试问题的一些总结
- C++面试问题总结
- 面试中关于C++中的类,结构体,enum,字符变量等所占内存空间问题总结