101、为什么模板类一般都是放在一个h文件中?
- 模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。
- 所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。
- 在分离式编译的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找(当遇到未决符号时它会寄希望于连接器)。这种模式在没有模板的情况下运行良好,但遇到模板时就傻眼了,因为模板仅在需要的时候才会实例化出来。
- 所以,当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。
- 然而当实现该模板的.cpp文件中没有用到模板的实例时,编译器懒得去实例化,所以,整个工程的.obj中就找不到一行模板实例的二进制代码,于是连接器也不行了。
102、cout和printf有什么区别?
区别:
- cout<<是一个函数,cout<<后可以跟不同的类型是因为cout<<已存在针对各种类型数据的重载,所以会自动识别数据的类型。
- cout是有缓冲输出,输出过程会首先将输出字符放入缓冲区,然后输出到屏幕。printf是行缓冲输出,不是无缓冲输出。
- 使用格式不同,printf需要你告诉它格式(int %d, double %f,char %c),而cout不需要;
- printf是函数。cout是ostream对象,和<<配合使用;
- printf是变参函数,没有类型检查,不安全。cout是通过运算符重载实现的,安全;
- 如果printf碰到不认识的类型就没办法了,而cout可以自己重载进行扩展。
103、你知道重载运算符吗?
class Stu
{
public:
int age;
string name;
Stu(){};
Stu(int a,string b)
{
age=a;
name=b;
}
~Stu(){}
Stu operator+(const Stu &ss)const //成员函数运算符重载
{
Stu n;
n.age=this->age+ss.age; //this可去掉
n.name=ss.name;
return n;
}
friend int operator+(const int s1,const Stu &s2);
};
int operator+(const int s1,const Stu &s2) //非成员函数运算符重载
{
int n;
n=s1*s2.age;
return n;
}
int main()
{
Stu s(3,"lili");
Stu s2(5,"yaya");
s=s+s2;
int x=s.age+s2;
cout<<s.age<<" "<<s.name<<" "<<x<<endl;
return 0;
}
//运行结果
Stu constructor1 called!
Stu constructor1 called!
Stu constructor called!
Stu destructor called!
8 yaya 40
Stu destructor called!
Stu destructor called!
- 只能重载已有的运算符,而无权发明新的运算符;
- 对于一个重载的运算符,其优先级和结合律与内置类型一致才可以;
- 不能改变运算符操作数个数;
- 两种重载方式:成员运算符重载和非成员运算符重载,成员运算符比非成员运算符少一个参数;
- 下标运算符、箭头运算符必须是成员运算符重载;
- 引入运算符重载,是为了实现类的多态性;
- 当重载的运算符是成员函数时,this绑定到左侧运算符对象。成员运算符函数的参数数量比运算符对象的数量少一个;至少含有一个类类型的参数;
- 下标运算符
[]
必须是成员函数,下标运算符通常以所访问元素的引用作为返回值,同时最好定义下标运算符的常量const版本和非常量版本; - 箭头运算符必须是类的成员,解引用通常也是类的成员;
- 重载的箭头运算符必须返回类的指针;
104、定义和声明的区别
变量的声明和定义:
- 从编译原理上来说,声明是仅仅告诉编译器,有个某类型的变量会被使用,但是编译器并不会为它分配任何内存。
- 定义就是分配了内存。
函数的声明和定义:
- 声明: 一般在头文件里,对编译器说:这里我有一个函数叫function() 让编译器知道这个函数的存在。
- 定义: 一般在源文件里,具体就是函数的实现过程 写明函数体。
105、全局变量和static变量的区别?static函数与普通函数有什么区别?
1、全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。
全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。两者都在全局/静态存储区!
区别:
- 非静态全局变量的作用域是整个源程序,当一个源程序由多个原文件组成时,非静态的全局变量在各个源文件中都是有效的。
- 静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。
- 由于静态全局变量的作用域限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。
- static全局变量只初始化一次,防止在其他文件单元被引用。
static函数与普通函数有什么区别?
-
static函数与普通的函数作用域不同。只在当前源文件中使用的函数应该说明为static函数,static函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的普通函数应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。
-
static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝。
106、 静态成员与普通成员的区别是什么?
生命周期:
- 静态成员变量的生命期不依赖于任何对象,为程序的生命周期;
- 普通成员变量只有在类创建对象后才开始存在,对象结束,它的生命期结束;
共享方式:
- 静态成员变量是全类共享;普通成员变量是每个对象单独享用的;
定义位置:
- 普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区;
初始化位置:
- 普通成员变量在类中初始化;静态成员变量在类外初始化;
默认实参:
- 可以使用静态成员变量作为默认实参。
107、隐式转换,如何消除隐式转换?
- C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的。
- 所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。很多时候用户可能都不知道进行了哪些转换。
- C++面向对象的多态特性,就是通过父类的类型实现对子类的封装。通过隐式转换,你可以直接将一个子类的对象使用父类的类型进行返回。
- 在比如,数值和布尔类型的转换,整数和浮点数的转换等。
- 某些方面来说,隐式转换给C++程序开发者带来了不小的便捷。C++是一门强类型语言,类型的检查是非常严格的。
- 基本数据类型的转换以取值范围的作为转换基础(保证精度不丢失)。
- 隐式转换发生在从小->大的转换中。比如从char转换为int。从int->long。
- 自定义对象 子类对象可以隐式的转换为父类对象。
- C++中提供了explicit关键字,在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。
- 如果构造函数只接受一个参数,则它实际上定义了转换为此类类型的隐式转换机制。可以通过将构造函数声明为explicit加以制止隐式类型转换;
- 关键字explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无需将这些构造函数指定为explicit。
108、如何在不使用额外空间的情况下,交换两个数?你有几种方法
1) 算术
x = x + y;
y = x - y;
x = x - y;
2) 异或
x = x^y;// 只能对int,char..
y = x^y;
x = x^y;
x ^= y ^= x;
109、C++如何处理多个异常的?
C++中的异常情况:
- 语法错误(编译错误):比如变量未定义、括号不匹配、关键字拼写错误等等编译器在编译时能发现的错误,这类错误可以及时被编译器发现,而且可以及时知道出错的位置及原因,方便改正。
- 运行时错误:比如数组下标越界、系统内存不足等等。这类错误不易被程序员发现,它能通过编译且能进入运行,但运行时会出错,导致程序崩溃。
- 为了有效处理程序运行时错误,C++中引入异常处理机制来解决此问题。
C++异常处理机制:
异常处理基本思想:
- 执行一个函数的过程中发现异常,可以不用在本函数内立即进行处理, 而是抛出该异常,让函数的调用者直接或间接处理这个问题。
- C++异常处理机制由3个模块组成:try(检查)、throw(抛出)、catch(捕获) ;
- 抛出异常的语句格式为:
throw 表达式;
如果try块中程序段发现了异常则抛出异常。
try { 可能抛出异常的语句;(检查) try
{
可能抛出异常的语句;(检查)
}
catch(类型名[形参名])//捕获特定类型的异常
{
//处理1;
}
catch(类型名[形参名])//捕获特定类型的异常
{
//处理2;
}
catch(…)//捕获所有类型的异常
{
}
110、你知道strcpy和memcpy的区别是什么吗?
1、复制的内容不同;
- strcpy只能复制字符串;
- 而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同;
- strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。
- memcpy则是根据其第3个参数决定复制的长度。
3、用途不同;
- 通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。