1.malloc和new有什么区别?
1)malloc是C/C++的库函数,new是C++的运算符,两者都能够用来申请动态内存。
2)new不仅会分配内存,还会调用构造函数,而malloc只分配内存,不进行成员初始化的工作。
3)new出来的指针是带类型信息的,而malloc返回的是void指针。
4)内存泄漏对new和malloc都可以检测出来,但是new可以指明文件哪一行,malloc没有这些信息。(注意:使用_CrtDumpMemoryLeaks函数检测,按F5,结果在Output窗口)
2.实现strcpy、memcpy、strcat、strstr函数。
1)strcpy函数
char *Strcpy(char *strDest, const char *strSrc)
{
assert((strDest!=NULL)&&(strSrc!=NULL));
char *address = strDest;
while ((*strDest++ = *strSrc++) != '\0') NULL;
return address;
}
上面代码没有考虑重叠情况:
char str[10] = "abc";
Strcpy(str + 1, str);
这段代码是造成死循环。
改成这样:
char *Strcpy(char *strDest, const char *strSrc)
{
assert((strDest!=NULL)&&(strSrc!=NULL));
char *address = strDest;
memcpy(strDest, strSrc, strlen(strSrc) + 1);
return address;
}
注意:strlen返回的长度不包括'\0'
重新运行:
char str[10] = "abc";
Strcpy(str + 1, str);
cout << str << endl;
输出:
aabc
注意:
strcpy函数有返回值是为了实现链式操作:
int length=strlen(strcpy(str1,str2));
2)memcpy函数
考虑重叠情况的实现如下:
void *Memcpy(void *des, const void *src, size_t size)
{
char *pdes = static_cast<char *>(des);
const char *psrc = static_cast<const char*>(src);
if (pdes > psrc && pdes < (psrc + size)) //地址重叠,从后面开始复制
{
for (size_t i = size - 1; i != -1; i--) //注意:此处不能为i>=0
pdes[i] = psrc[i];
}
else
{
for (size_t i = 0; i < size; i++) pdes[i] = psrc[i];
}
return pdes;
}
注意:因为采用的是逐字节复制,所以采用char*来访问。
strcpy与memcpy的区别:
a)复制的内容不同。strcpy只能复制字符串,memcpy可以复制任意内容。
b)复制的方法不同。strcpy不需要指定长度,遇到'\0'结束。memcpy需要指定长度。
其实上面实现的是memmove函数,因为memcpy和memmove的唯一区别是,当内存发送局部重叠时,memmove保证拷贝安全,memcpy不保证拷贝安全。
3)strcat函数
char *strcat(char *strDest, const char *strSrc)
{
assert((strDest != NULL) && (strSrc != NULL));
char *address = strDest;
while (*strDest != '\0') ++strDest;
while (*strSrc != '\0') *strDest++ = *strSrc++;
return address;
}
4)strstr函数
功能:从一个字符串中,查找另一个字符串的位置(const char *)。
const char *Strstr(const char *src, const char *sub)
{
if (src == NULL || sub == NULL) return src;
const char *cp;
const char *bp;
while (*src != '\0')
{
cp = src;
bp = sub;
do
{
if (*bp == '\0') return src;
} while (*bp++ == *cp++);
++src;
}
return NULL;
}
3.static和const的作用。
static的作用:
1)函数内的static变量在函数内部可以使用,函数退出时占用内存不被释放,再次进入函数时,static变量的值维持上次退出时的值,程序退出时内存被系统回收。
2)模块内的static全局变量可以被模块内的所有函数使用,占用内存在程序退出时被系统回收。
3)模块内的static函数只能被模块内的函数调用。
4)类里面的static成员变量与类绑定,类的所有对象对static成员变量只有一份拷贝。
5)类里面的static成员函数与类绑定,该函数不接收this指针,因此只能调用static成员变量。
const作用:
1)变量为const表示该变量只能被访问,不能被修改。
2)对于指针,const既可以修饰该指针不可以被修改,也可以修饰该指针所指对象不能被修改,也可以修饰两者都不可以被修改。
3)对于函数的输入参数,const表明在函数内部该变量不能被修改。
4)对于函数的返回参数,const表明该函数的返回值不能用作左值。
5)const可以修饰类成员函数,表明在该成员函数内,类的成员不能被修改。
4.static全局变量与普通全局变量区别?static函数与普通函数区别?
static全局变量和全局变量都存储在全局(静态)区,区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态全局变量在各个源文件中都有效(其他文件用extern声明后就可以使用该变量),而静态全局变量只在定义该变量的源文件有效。
5.sizeof和strlen的区别。
1)sizeof是操作符,strlen是函数
2)sizeof可以用类类型做参数,strlen只能用char*作参数,且必须是以'\0'结尾的。
3)大部分程序在编译时sizeof就被计算过了,因此可以用sizeof来定义数组维数,strlen要在运行的时候才能计算出来。
4)数组名做sizeof参数不退化,传递给strlen就退化为指针了。
(
什么意思呢?就是说数组名char a[10]="abc"作为sizeof的参数时,不退化成指针char *a,输出10,如果退化成char *,则求的是sizeof(char *),就输出4了。
而strlen(a)时,因为strlen的参数是char *类型,所以参数退化成指针char *了。
)
5)strlen计算的是字符串的长度,而sizeof计算的是字符指针占用空间大小。
#include <iostream>
using namespace std;
void func(char *str){
cout << sizeof(str) << endl;
cout << strlen(str) << endl;
}
int main()
{
char a[] = "123456789";
cout << sizeof(a) << endl;
cout << strlen(a) << endl;
func(a);
return 0;
}
//输出:
//10
//9
//4
//9
6.内联函数与宏有什么区别?
1)宏在预编译展开,内联函数在编译时展开。
2)在编译的时候,内联函数可以被镶嵌到目标代码中,而宏只是简单的文本替换。
3)内联函数可以完成类型检测,语法检测等编译功能,宏不具有这样的功能。
4)宏不是函数,内联函数是函数。
5)宏在定义时要小心处理宏参数(把参数用括号括起来),否则容易出现二义性,而内联函数不会出现二义性。
补充:
优点:内联函数省去了函数调用的开销,提高函数执行效率。
缺点:使程序的总代码量增大,消耗更多的内存空间。
7.volatile有什么含义?
如果变量被声明为volatile,优化器在用到这个值时,必须每次小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
用在以下几个地方:
1)中断程序修改的供其他程序检测的变量需要加上volatile。
2)多任务环境下各任务共享的标志位应该加上volatile。
3)存储器映射的硬件寄存器通常也要加上volatile。
8.什么情况只能初始化列表而不能赋值?
当类中含有const、reference成员变量和需要调用基类构造函数时。
注意:静态成员不能在类内部初始化,所以也不能在初始化成员列表中。
9.复制构造函数和赋值函数区别?
1)复制构造函数是用一个对象来初始化一块内存区域,即新对象的内存区;赋值函数就是对一个已经存在的对象进行赋值操作。
2)如果数据成员包含指针对象时,一般复制构造函数是复制指针对象,而赋值函数引用指针对象。
3)实现不一样。复制构造函数首先是构造函数,通过传进来的参数初始化产生一个对象。赋值函数则是把一个对象赋值给另一个对象。
10.什么是临时变量?临时变量在什么情况下产生?
临时变量是仅仅需要一小段时间的变量。
通常在以下两种情况下产生:
1)参数按值传递。
2)返回值按值传递。
11.私有继承和组合的区别:
相同点:都可以表示“有一个”关系
不同点:私有继承中派生类能够访问基类的protected成员,并且可以重写基类的虚函数,当基类是抽象类时也可以使用。组合不具有这些功能。
12.内联函数占不占用运行时间。
内联函数跟宏定义一样,在编译时直接进行代码替换。内联函数只是不消耗时间去调用,而不是不占用运行时间。
13.父类的析构函数为什么要定义为虚函数?
如果一个继承类对象经由基类指针被删除时,如果析构函数是非虚函数,则只能调用基类的析构函数,从而使对象的继承类成分没被销毁。而如果析构函数是虚函数,则可以销毁整个对象。
14.override(重写/覆盖)和overload(重载)的区别
1)override的方法名,返回值,参数相同,voerload的参数类型,个数,顺序至少有一个不同。
2)override存在于子类和父类之间,overload存在于子类和父类,同类之间。
3)override时子类方法不能缩小父类方法的访问权限。
4)重写的方法抛出的异常必须和被重写的方法抛出的异常一致,或者是其子类。
5)子类static静态方法隐藏父类的static静态方法是可以的,但不晓得是不是override。(有人说只是隐藏)
15.指针和引用的区别
1)初始化要求不同。引用定义时必须初始化,指针定义时可以不初始化。
2)可修改性不同。引用一旦被初始化为指向一个对象,就不能修改为其他对象的引用。而指针可以改变为指向另一个对象。
3)指向NULL不同。引用不能指向空值,指针可以指向NULL。
4)测试需要的区别。因为引用不会指向空值,因此使用引用前不需要测试其是否指向空值,但是指针可以指向空值,因此使用指针经常要进行测试。
注意:引用比指针安全的原因是,不存在空引用,且一旦引用被初始化为指向一个对象,就不能改变为另一个对象的引用。
16.为什么析构函数不能抛出异常
1)如果析构函数抛出异常,则异常后面的程序不会执行。如果析构函数在异常点之后执行了一些必要的动作比如释放资源,这些动作不会执行,会造成资源泄露。
2)通常异常发生时,C++机制会调用已经构造对象的析构函数来释放资源,此时若析构函数也抛出异常,则前面一个异常未处理,又有新的异常,会造成程序崩溃问题。
第一点有时候不能保证,可以照下面方法,不让异常跑出析构函数。
~ClassName()
{
try{
do_something();
}
catch(){ //这里可以什么都不做,只是保证catch块的程序抛出的异常不会被扔出析构函数之外。
}
}
17.几种常见的库函数
1)字符串连接函数:strcat
2)查找字符串子串函数:strstr
3)数字转字符串:itoa
18.想引用外部文件的函数怎么做?
使用extern+加上函数名(带上返回类型)。
#include <iostream>
//#include "func.h"
using namespace std;
extern void test();
int main()
{
test();
return 0;
}
注意:不需要包含对应的文件,只需要将文件放到相同目录下。
19.两个头文件可以相互引用吗?
可以,但是最好不要这样,用前置声明来代替引用。
20.C++程序调用C程序该怎么做?
如果在C++文件里调用C函数,会找不到函数体,报链接错误。
原因是编译器产生C++函数名和C函数名时,涉及到重载,编译后在库中的名字不同。假设某个函数的声明如下:
void foo(int x, int y);
该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字用来支持函数重载。由于编译后的名字不同,C++程序不能直接调用C函数。
要解决这个问题,就要在C++文件里面显示声明哪些函数是C写的,要用C的方式来处理。
1)在引用头文件前需要加上extern "C",如果引用多个,按下面方法
extern “C”
{
#include “ s.h”
#include “t.h”
#include “g.h”
#include “j.h”
};
然后在调用这些函数之前,需要将函数也全部声明一遍。
2)C++调用C函数的方法,将用到的函数全部重新声明一遍。
extern “C”
{
extern void A_app(int);
extern void B_app(int);
extern void C_app(int);
extern void D_app(int);
}
21.构造函数和析构函数与虚函数的关系。
1)构造函数不能是虚函数。
原因:
a.构造一个对象的时候,必须知道对象的实际类型,而虚函数行为实在运行期确定实际类型的。而在构造一个对象时,由于对象还没有构造成功,编译器无法知道对象的实际类型是该类本身还是该类的一个派生类。
b.虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。
2)析构函数可以声明为虚函数,而且有时必须声明为虚函数。
解释:
在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚析构函数,派生类中派生的那部分无法析构。
3)不建议在构造函数和析构函数里面调用虚函数。(一般指基类构造函数或析构函数调用虚函数)
不在构造函数调用虚函数原因:在基类构造的时候,派生类对象还没有构造,不会走到派生类中。
不在析构函数调用虚函数原因:在基类析构的时候,派生类已经析构完成,调用虚函数会导致其调用已经析构了的子类对象对象里的函数,这非常危险。
22.拷贝构造函数为什么使用引用类型?
如果不使用引用类型,在传递参数的过程中会调用拷贝构造函数,这样会造成无限递归,最终导致栈溢出。所以拷贝构造函数必须使用引用类型。
23.静态成员函数为什么不能声明为虚函数?
静态成员函数,可以不通过对象来调用,即没有隐藏的this指针。
虚函数一定要通过对象来调用,即具有隐藏的this指针。
24.C++代码如何从源代码到可执行文件的?
1)预处理:对一些伪指令(以#开头的指令)和特殊符号(如LINE表示当前行号,FILE表示当前编译的C++程序名称)进行处理;
2)编译:进行词法分析和语法分析;
3)优化:
4)汇编:把汇编语言代码翻译成机器指令。
25.构造函数构造失败应该怎样处理?
1)在构造函数中抛出异常,让上层知道构造函数失败。也可以传入一个引用值,然后在构造函数里面设置状态。
2)构造函数抛出异常的话将会导致对象的析构函数不被执行。
3)当对象发生部分构造时,已经构造完毕的子对象将会逆序地被析构。
26.虚函数的特性
1)一旦将某个成员函数声明为虚函数后,它在类的继承体系中就永远为虚函数了。
2)如果基类定义了虚函数,当通过基类指针或引用调用派生类对象时,将访问派生类的虚函数版本(多态)。
3)派生类中的虚函数要保持其虚特性,必须与基类虚函数的函数原型完全相同,否则就只是普通的重载函数,与基类的虚函数无关。
4)只有类的非静态成员函数才能被定义为虚函数,类的构造函数和静态成员函数不能定义为虚函数。
5)内联函数也不能是虚函数。因为内联函数采用的是静态联编的方式,而虚函数是程序运行时才与具体函数动态绑定的,采用的是动态联编的方式。(即使虚函数在类里面被定义,编译器也将它视为非内联函数,不能用inline来声明虚函数。)
27.struct和union的区别
struct:每个成员拥有独立的内存,遵循内存对齐原则,struct变量的总长度是所有成员的长度之和。
union:所有成员共用一块内存,同时只有一个成员拥有这块内存的使用权,每个变量的首地址相同。一个union的总长度最少能容纳最大成员变量。并且是所有成员变量大小的整数倍。
#include<stdio.h>
//结构体
struct u //u表示结构体类型名
{
char a; //a表示结构体成员名
int b;
short c;
}U1;
//U1表示结构体变量名
//访问该结构体内部成员时可以采用U1.a=1;其中"点"表示结构体成员运算符
//联合体
union u1 //u1表示联合体类型名
{
char a; //a表示联合体成员名
int b;
short c;
}U2;
//U2表示联合体变量名
//访问该联合体内部成员时可以采用U2.a=1;其中"点"表示联合体成员运算符
struct u //u表示结构体类型名
{
int b;
char a; //a表示结构体成员名
short c;
}U3;
//主函数
int main(){
printf("%d\n",sizeof(U1));
printf("%d\n",sizeof(U2));
printf("%d\n",sizeof(U3));
return 0;
}
//程序运行结果是:
//12
//4
//8
union可以用来判断是大端还是小端。比如:请写一个c程序,如果处理器是大端(低位地址存高位字节),返回0,小端(低位地址存低位字节),返回1。
int checkCPU(void)
{
union
{
int a;
char b;
}c;
c.a = 1;
return (c.b == 1);