定义与声明的区别 ?
- 声明是告诉编译器变量的类型和名字,不会为变量分配空间
- 定义就是对这个变量和函数进行内存分配和初始化。需要分配空间,同一个变量可以被声明多次,但是只能被定义一次
- 如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义
typedef 和 define 的区别 ?
define是预处理命令,在预处理是执行简单的替换,不做正确性的检查
typedef是在编译时处理的,它是在自己的作用域内给已经存在的类型一个别名
具体来说:
- 执行时间不同:关键字 typedef 在编译阶段有效,由于是在编译阶段,因此 typedef 有类型检查的功能。#define 则是宏定义,发生在预处理阶段,也就是编译之前,它只进行简单而机械的字符串替换,而不进行任何检查。
- 功能有差异:typedef 用来定义类型的别名,定义与平台无关的数据类型,与 struct 的结合使用。define 不只是可以为类型取别名,还可以定义常量,变量, 编译开关等
- 作用域不同:#define 没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而 typedef 有自己的作用域。
void func1()
{
#define HW "HelloWorld";
}
void func2()
{
string str = HW;
cout << str << endl;
}
//typedef 有自己的作用域
void func1()
{
typedef unsigned int UINT;
}
void func2()
{
UINT uValue = 5;//error C2065: 'UINT' : undeclared identifier
}
C++ 重载、重写(覆盖)、隐藏的定义与区别?
重载原理是 c++ 根据参数和返回值 的重命名机制;重载是指函数名相同,参数列表不同的实现方法,它们的返回值可以不同,返回值不可以作为区分不同重载函数的标志。
重写指派生类重写基类同名方法。重写是指函数名相同,参数列表相同,只有方法体不相同的实现方法。一般用于子类继承父类时对父类方法的重写。子类的同名方法屏蔽了父类方法的现象称为隐藏。
牢记以下几点,就可以区分函数重载、函数隐藏、函数覆盖和函数重写的区别:
- 函数重载发生在相同的作用域
- 函数隐藏发生在不同的作用域
- 函数覆盖就是函数重写。准确地叫做虚函数覆盖和虚函数重写,也就是函数隐藏的特列
关于三者的对比,如下表所示:
ifdef 作用
希望在满足某些条件的情况下进行条件编译
define 与 const 的联系与区别 ?(编译阶段、安全性、内存占用等)
联系:它们都是定义常量的一种方法。
区别:
- define定义的常量没有类型,只是进行了简单的替换,可能会有多个拷贝,占用的内存空间大,const定义的常量是有类型的,存放在静态存储区,只有一个拷贝,占用的内存空间小。
- define定义的常量是在预处理阶段进行替换,而const在编译阶段确定它的值。
- define不会进行类型安全检查,而const会进行类型安全检查,安全性更高。
- const 可以定义函数 而 define不可以。
在 C++ 中 const 的用法(定义、用途)
- const 修饰类的成员变量时,表示常量不能被修改
- const 修饰类的成员函数,表示该函数不会修改类中的数据成员,不会调用其他非 const 的成员函数
const 相关 ?
const修饰普通类型的变量,告诉编译器某值是保持不变的。
const 修饰指针变量,根据const出现的位置和出现的次数分为三种
- 指向常量的指针:指针指向一个常量对象,目的是防止使用该指针来修改指向的值。
- 常指针:将指针本身声明为常量,这样可以防止改变指针指向的位置。
- 指向常量的常指针:一个常量指针指向一个常量对象。
const修饰参数传递,可以分为三种情况。
- 值传递的 const 修饰传递,一般这种情况不需要 const 修饰
- 当 const 参数为指针时,可以防止指针被意外篡改。
- 自定义类型的参数传递,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们采取 const 外加引用传递的方法。
const修饰函数返回值,分三种情况。
- const 修饰内置类型的返回值,修饰与不修饰返回值作用一样。
- const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。
- const 修饰返回的指针或者引用,是否返回一个指向 const 的指针,取决于我们想让用户干什么。
const修饰成员函数
const 修饰类成员函数,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为 const 成员函数。
static 和 extern ?
static 关键字的作用?
-
定义静态函数或全局变量:当我们同时编译多个文件时,在函数返回类型或全局变量前加上static关键字,函数或全局变量即被定义为静态函数或静态全局变量。静态函数或静态全局变量只能在本源文件中使用。这就是static的隐藏属性。
-
static 的第二个作用是保持变量内容的持久化:在变量前面加上static关键字。初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS段分配内存。直到程序结束,静态变量始终会维持前值。只不过全局静态变量和局部静态变量的作用域不一样。
-
static 的第三个作用是默认初始化为 0:全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是 0x00 。
-
最后对 static 的三条基本作用做一句话总结。首先 static 的最主要功能是隐藏,其次因为 static 变量存放在静态存储区,所以它具备持久性和默认值0。
-
在 C++ 中,static 关键字可以用于定义类中的静态成员变量:使用静态数据成员,它既可以被当成全局变量那样去存储,但又被隐藏在类的内部。类中的static静态数据成员拥有一块单独的存储区,而不管创建了多少个该类的对象。所有这些对象的静态数据成员都共享这一块静态存储空间。
-
在c++中,static关键字可以用于定义类中的静态成员函数:与静态成员变量类似,类里面同样可以定义静态成员函数。只需要在函数前加上关键字static即可。如静态成员函数也是类的一部分,而不是对象的一部分。所有这些对象的静态数据成员都共享这一块静态存储空间。
extern 关键字的作用
extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
struct 和 class 的区别 ?
- 使用
struct
时, 它的成员的访问权限默认是public
的 ,而class
的成员默认是private
的 struct
的继承默认是public
继承,而class
的继承默认是private
继承class
可以用作模板,而struct
不能
C++ 中 final 关键字对性能的影响?
C++ 引入了关键字 final , 按官方的标准是该关键字是用来标识虚函数能不能再子类中被覆盖(override),或一个类不能被继承。用法如下:
mutable 关键字
在 C++ 中,mutable 是为了突破 const 的限制而设置的。被 mutable 修饰的变量,将永远处于可变的状态,即使在一个const 函数中,甚至结构体变量为 const, 其 mutable 成员也可以被修改。mutable在类中只能够修饰非静态数据成员。
#include <iostream>
using namespace std;
class test{mutable int a;int b;public:test(int _a,int _b) :a(_a),b(_b){};
void fun() const //fun是const 函数,不能修改类的对象的数据成员,但由于a被mutable修饰,可以修改,但不能修改b
{
a += b;
}
void print()
{
cout << a << "," << b << endl;
}
};
我们知道,如果类的成员函数不会改变对象的状态,那么这个成员函数一般会声明成const的。但是,有些时候,我们需要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutalbe来修饰。
volatile 关键字 ?
volatile原意是“易变的”,但这种解释简直有点误导人,应该解释为“直接存取原始内存地址”比较合适。“易变”是相对与普通变量而言其值存在编译器(优化功能)未知的改变情况(即不是通过执行代码赋值改变其值的情况),而是因外在因素引起的,如多线程,中断等。编译器进行优化时,它有时会取一些值的时候,直接从寄存器里进行存取,而不是从内存中获取,这种优化在单线程的程序中没有问题,但到了多线程程序中,由于多个线程是并发运行的,就有可能一个线程把某个公共的变量已经改变了,这时其余线程中寄存器的值已经过时,但这个线程本身还不知道,以为没有改变,仍从寄存器里获取,就导致程序运行会出现未定义的行为。并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化。而加了volatile修饰的变量,编译器将不对其相关代码执行优化,而是生成对应代码直接存取原始内存地址。
一般来说,volatile 用在如下几个地方:
- 中断服务程序中修改的供其它程序检测的变量需要加 volatile
- 多任务环境下各任务间共享的标志应该加 volatile
- 存储器映射的硬件寄存器通常也要加 volatile 说明,因为每次对它的读写都可能有不同意义
define 与 inline 的区别?
inline是内联的意思,可以定义比较小的函数。因为函数频繁调用会占用很多的栈空间,进行入栈出栈操作也耗费计算资源,所以可以用inline关键字修饰频繁调用的小函数。编译器会在编译阶段将代码体嵌入内联函数的调用语句块中。
-
内联函数在编译时展开,而宏在预处理时展开
-
在编译的时候,内联函数直接被嵌入到目标代码中去,而宏只是一个简单的文本替换。
-
内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能。
-
宏不是函数,而inline是函数
-
宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性。而内联函数不会出现二义性。
-
inline可以不展开,宏一定要展开。因为inline指示对编译器来说,只是一个建议,编译器可以选择忽略该建议,不对该函数进行展开。
-
宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。
在 inline 加上 static 修饰符,只是为了表明该函数只在该文件中可见!也就是说,在同一个工程中,就算再其他文件中也出现同名、同参数的函数也不会引起函数重复定义的错误;当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通的函数执行效率一样了
virtual 关键字 的作用
- 在派生类中重新定义基类方法
- 为多态基类声明 virtual 析构函数
- 抽象基类
内联函数、宏与普通函数的区别 ?
-
内联函数和宏的区别
- 宏定义不是函数,但是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈退栈过程,提高了效率。
- 内联函数本质上是一个函数,内联函数一般用于函数体的代码比较简单的函数,不能包含复杂的控制语句,while、switch,并且内联函数本身不能直接调用自身。如果内联函数的函数体过大,编译器会自动的把这个内联函数变成普通函数。
- 宏定义是在预编译的时候把所有的宏名用宏体来替换,简单的说就是字符串替换
内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样可以省去函数的调用开销,提高效率 - 宏定义是没有类型检查的,无论对还是错都是直接替换
内联函数在编译的时候会进行类型的检查,内联函数满足函数的性质,比如有返回值、参数列表 - 宏定义和内联函数使用的时候都是进行代码展开,不同的是宏定义是在预编译的时候把所有的宏名替换,内联函数则是在编译阶段在所有调用内联函数的地方把内联函数插入。这样可以省去函数压栈退栈,提高了效率。
-
内联函数和普通函数的区别
- 内联函数和普通函数的参数传递机制相同,但是编译器会在每处调用内联函数的地方将内联函数内容展开,这样既避免了函数调用的开销又没有宏机制的缺陷
- 普通函数在被调用的时候,系统首先要到函数的入口地址去执行函数体,执行完成之后再回到函数调用的地方继续执行,函数始终只有一个复制。
- 内联函数不需要寻址,当执行到内联函数的时候,将此函数展开,如果程序中有N次调用了内联函数则会有N次展开函数代码
- 内联函数有一定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句。如果内联函数函数体过于复杂,编译器将自动把内联函数当成普通函数来执行
指针数组合数组指针?
简单介绍
C/C++ 中的这两种书写方法实在是很像,但是它们的含义有很大的不同,所以一定要能清晰地辨别它们。
简单地说,int *p[4])
是指针数组,int(*p)[4]
是指向数组的指针
指针数组:是一个元素全为指针的数组;数组指针:可以理解为指针,只是这个指针类型不是 int
而是 int[4]
类型的数组
int *p[4]
, p 是一个指针数组,每一个指向 int
型的, 等价于(int*)(p[4])
int (*P)[4]
,p 是一个热指针,指向int[4]
的数组
通过运算优先级来理解
上面的原因是 *(间接引用运算符)的优先级低于 [] 的优先级。
首先看 int *p[4]
,[] 的优先级高,所以它首先是一个大小为 4 的数组 , 即 p[4]
, 剩下的 int *
作为补充说明,即说明该数组的每一个元素为指向一个整数类型的指针。int *p[4]
的存储结构如下:
再看 int (*p)[4]
。它首先是一个指针,即 *p
, 剩下的 int[4]
作为补充说明,即说明指针 p
是指向一个长度为 4 的数组。int (*p)[4]
的存储结构如下:
sizeof() 和 strlen() 的区别 ?
区别:
- sizeof 是一个操作符,而 strlen 是库函数
- sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为 ‘\0’ 的字符串做参数
- 编译器在编译时就计算出了sizeof 的结果,而strlen必须在运行时才能计算出来
- sizeof计算数据类型占内存的大小,strlen计算字符串实际长度
举例
char str[]="hello";
char *p=str;int n=10;//请计算sizeof(str);//6,5+1=6,1代表'\0'sizeof(p);//4,代表指针sizeof(n);//4,整形占据的存储空间void func(char str[100])
{ sizeof(str);//4,此时str已经转换为指针了}
void *p=malloc(100);sizeof(p);//4,指针大小
成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?
C++ 是类型安全的吗 ?
C++ 不是类型安全的。
类型之间可以进行强制转换,表示类型不安全。
C++ 多态 ?
- 静态多态——函数模板和函数重载
- 动态多态——子类代码重写,基类指针指向不同子类相同函数实现不同的动作