1.static关键字的作用和使用场景?
- static的作用——隐藏,变量所有未加static前缀的全局变量和函数都具有全局可见性,假设有两个文件,其中一个文件中定义全局变量a,int a = 10;如果在另一个文件中声明extern int a;则a = 10,在这个文件中可以对其进行使用。但是如果定义的全局变量前加上static,即就是:static int a = 10;则在另一个文件中是不可见的。同理全局的函数也可以添加static。可以根据这一个特性在不同的文件中定义相同的函数名或者变量名,不需要担心命名冲突。
- static的生命周期是全局的,直到程序结束后才会被释放,但是static局部变量的作用域与自动变量是相同的,只能在其作用域内进行使用,出了作用域虽然变量还存在,但是不能使用。static定义的变量会保存在静态区中,存储在静态区中的变量只会初始化一次,全局变量和static变量都存储在静态区中。
- static类型的变量会默认初始化为0,在静态存储区中存储的变量都会默认初始化为0,所以全局变量也会默认为0,
- 类的静态成员函数是整个类的,而非类的对象的,它没有this指针。所以静态成员函数只能访问类中的静态成员函数或者静态成员变量。
- 不能将静态函数定义为虚函数。(?)
- static的成员变量的初始化必须在类外,前面不需要加static。
- 静态数据成员必须进行初始化(编译时一般不会出错,链接会出错)
- (3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊 ,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。(?)
- static成员变量能够进行继承。final修饰的变量是不能进行继承的。
2.构造函数
- 构造函数是与类名同名的函数,没有返回值和返回值类型,
- 构造函数分为两个阶段,第一个阶段为成员变量的初始化,第二个阶段为计算阶段。初始化阶段是将类中所有声明的成员变量按照其声明的顺序进行初始化,计算阶段即就是执行函数体的赋值部分。
- 初始化类的成员函数有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。对于基本类型两种方式差别不大,但是对于类类型来说,使用初始化列表的效率会高于在函数体内进行赋值,因为会少调用一次默认构造函数。
- const成员变量必须使用初始化列表,不能对其进行赋值。没有默认构造函数的类类型,必须使用初始化列表对其初始化,因为使用初始化列表只会调用拷贝构造函数。子类中的构造函数必须使用初始化列表初始化基类成员变量。
- 构造函数是可以进行重载的,我们经常会写无参构造,有参构造,构造函数中的参数还可以是缺省的,缺省的参数必须从最后一个参数往前进行缺省,但是注意无参构造和全缺省构造函数是不能同时存在的,会存在二义性,编译器不知道我们想要执行的是无参还是全缺省。定义缺省时一定要注意不要存在冗余。
- 构造函数是在示列化对象时,编译器会自动进行调用,不能被显示的调用,在创建对象时,编译器会先给对象分配空间,然后自动调用构造函数。
- 构造函数是不能被继承的,子类中必须重新定义构造函数
- 构造函数一般定义为public属性
- 执行派生类构造函数的顺序:先调用基类构造函数,然后对子对象数据成员进行初始化,然后在执行派生类构造函数本身。(若派生类中没有子对象,则将不会进行子对象的初始化)
- 派生类的构造函数中,参数初始化列表中调用派生类的直接父类的构造函数,不需要显示的调用间接父类的构造函数,因为在调用直接父类的构造函数时,会自动先调用间接父类的构造函数。
- 构造与析构顺序是相反的,先构造的后析构。
- 多重继承中调用基类构造函数的顺序是按照 “声明”派生类时基类出现的顺序
3.智能指针的循环引用
——循环引用实际上当两个对象互相使用一个shared_ptr成员变量指向对方的会造成循环引用,导致引用计数失效。
假设:sharedptr<ListNode> cur(new ListNode);
sharedptr<ListNode> next(new ListNode);
cur->_next = next;
next->_prev = cur;
4.指针和引用的区别
(1)指针是地址,引用是别名
(2)指针可以在运行时改变其所指向的值,引用一旦和某个对象绑定就不在改变
(3)从内存上来看,指针会分配内存区域,而引用不会,它仅仅是一个别名
(4)在参数传递时,引用会做类型检查,而指针不会
(5)引用不能为空,但是指针可以为空
5.malloc和new的区别
(1)malloc和free是C中的库函数,new和delete是C/C++的运算符,它们都可用于申请动态内存和释放动态内存。
(2)对于非内部数据类型的数据而言,光用malloc无法满足动态对象的要求,对象在创建的时候要自动执行构造函数,对象在消亡之前需要自动调用析构函数,由于malloc、free是库函数而不是运算符,不在编译器控制权限之内,不能把执行构造函数和析构函数的任务强加给malloc、free上。
(3)C程序只能使用malloc和free来进行动态内存的管理。
(4)new可以认为是malloc加上构造函数的执行,new出来的指针是直接带类型信息的。
6.C++虚函数
C++中虚函数使用虚函数表和虚函数表指针实现,虚函数是一个类的虚函数的地址表,用于索引类本身以及父类的虚函数地址,假如子类的虚函数重写了父类的虚函数,则对应在虚函数表中会把对应的虚函数替换为子类的虚函数地址,虚函数表指针存储于每个对象中(通常出于效率考虑,会放在对象的开始处),它指向对象所在类的虚函数表的地址,在多继承的环境下,会存在多个虚函数表指针,分别对应不同基类的虚函数表。
——一个对象访问普通成员函数较虚函数更加快捷,因为普通成员函数的地址在编译时就已经确定,而虚函数的地址需要在虚函数表中寻找虚函数所在地址,因此,相比普通成员函数速度要慢一些。
——内联函数,析构函数、静态成员函数都是不能定义为虚函数的。内联函数是在编译时展开的,而虚函数是在运行时进行绑定的,编译时无法展开,构造函数在进行调用时还不存在父类与子类的概念,父类只会调用父类的构造函数,子类会调用子类的构造函数,因此不存在动态绑定的概念。静态成员函数是以类为单位的函数,与对象无关。
7.如何动态开辟二维数组?
方法一:使用二级指针
int ** arr = malloc(rows * sizeof(int *));
for(int i = 0; i < rows; i++)
{
arr[i] = malloc(cols * sizeof(int));
}
方法二:使用数组指针
int (*arr)[cols] = malloc(rows * sizeof(*arr));
方法三:
int *arr = malloc(rows * cols * sizeof(int));
8.sizeof和strlen的区别?
1)sizeof是在‘编译’时就能够计算出大小,而strlen是在‘运行’时才能计算出大小。
2)sizeof是计算变量在内存中所占的内存大小(字节数),而strlen是计算字符串的长度,遇到'\0'结束。
3)若是数组,则传给sizeof不会退化,而传给strlen就会退化为指针。
4)sizeof可以计算类型,或者变量的大小,而strlen的参数必须是一个以'\0'结尾的字符串。
5)sizeof是关键字,不是一个函数,而strlen是一个函数。
9.设计一个不能被继承的类?
CParent是一个无法被继承的类。CParent类虚继承CNoHeritance类,并且CParent是CNoHeritance的朋友。同时,还有一个CChild类继承与CParent。
class CNoHeritance
{
private:
CNoHeritance(){}
~CNoHeritance(){}
friend class CParent;
};
class CParent : virtual public CNoHeritance
{
public:
CParent(int v){m_v = v;}
~CParent(){};
private:
int m_v;
public:
void fun(){cout << "The value is: " << m_v << endl;}
};
class CChild : public CParent
{
public:
CChild():CParent(10){}
~CChild(){}
};
10.简述多态的实现机理?
多态一个接口的多种实现,是面向对象的核心。主要是利用虚函数来进行实现的,虚函数是用virtual关键字来进行实现的,每一个存在虚函数的类中都有一个虚函数表,存放每个虚函数的地址,类的对象还有一个能够指向虚表的指针(虚指针),虚函数是运行时确定的。
11.浅谈对面向对象的认识?
面向对象强调抽象、封装、继承、多态。
抽象:在定义一个类的时候,实际上就是把一类事物中共有的属性和行为提取出来,形成一个物理模型(模版),这种研究问题的方法称为抽象。
封装:就是将类的属性包装起来,不让外界轻易的知道他的内部实现。只提供给你对外的接口让你来调用。好处可以增强模块的独立性。如设置属性或方法的访问权限(private、protected、public、默认)。
继承:就是子类从父类把它的有用的东西拿过来自己用,不用在自己去实现了。
多态:一个对象可以指向多种实际类型的现象。一个人,在不同场合下,有不同的身份,不同的状态。比如在家里,你是父母的孩子;在学校,你就是学生;在公司,你就是老板的职员。再比如在接口总定义一个run()方法,是什么在跑,汽车还是马?通过不同类的实现来表示相似的逻辑。
12.static修饰的成员变量和成员函数,const修饰的成员变量和成员函数?
static修饰的成员变量必须在类内部进行声明,在类外进行定义,而且只能定义一次。
const成员变量只是在某个对象的生存期内是常量,而对于整个类而言是可变的。一个类可以创建多个对象,每个对象的const成员变量的值是可以不同的。因此不能在类声明中初始化const成员变量(普通的const变量必须在定义的时候初始化)。const成员变量的初始化工作必须在构造函数初始化列表中进行。
static成员函数只能访问static成员变量,也只能调用其他的static成员函数。
const成员函数只能被该类的const对象调用。且不能修改成员变量,也不能调用该类中的非const成员函数。
13.malloc、calloc和realloc的区别?
char* p;
p = (char*)malloc(20);
p = (char*)calloc(20,sizeof(char));
p = (char*)realloc(p, sizeof(char)*20);(realloc是给已经分配好的空间,再次进行分配空间)
<1>malloc分配的内存是位于堆中的,并且没有初始化内存的内容,因此基本上malloc之后,调用函数memset来初始化这部分的内存空间.
<2>calloc则将初始化这部分的内存,设置为0.
<3>realloc则对malloc申请的内存进行大小的调整.