对虚函数理解?
- 1、虚函数:定义在基类中的函数,子类必须对其进行覆盖;
- 2、作用:定义子类对象,并调用对象中未被子类覆盖的基类函数A,又调用了已被子类覆盖的基类函数B,那此时会调用基类中函数B,可本应该调用的事子类的覆盖函数B;
- 3、在使用指向子类对象的基类指针,并调用子类中的覆盖函数时,如果该函数不是虚函数,那么将调用基类中的该函数;如果该函数时虚函数,则会调用子类中的该函数。
- 4、虚函数实际意义:疑问想要调用子类中的覆盖函数,直接通过子类对象,或者指向子类对象的子类指针来调用,要虚函数干什么?实际开发过程中,会用到别人封装好的框架和类库,可通过继承其中的类,并覆盖基类中的函数,来实现自定义的功能(连接子类函数与基类函数,在父类函数中调用子类函数)。有些函数需要框架来调用,并且API需要传入基类指针类型的参数,而使用虚函数可以将指向子类对象的基类指针来作为参数传入API,让API能够通过基类指针来调用我们自定义的子类函数。(体现了多态性)
为什么要定义虚析构函数?
通过父类指针将所有子类对象的析构函数都执行一遍,通过父类指针释放所有子类资源。
谈谈对多态的理解?
- 多态:(在继承父类基础上效果)同样的调用语句有多种不同的表现形态
- 多态实现条件:有继承、有virtual重写、有父类指针(引用)指向子类对象不同
- 多态的C++实现:virtual关键字,告诉编译器这个函数要支持多态,不是根据指针类型判断如何调用,而是要根据指针所指向的实际对象类型来判断如何调用
- 多态的理论基础:动态联编和静态联编,根据实际对象类型来判断重写函数的调用;
- 多态的重要意义:设计模式的基础,是框架的基石;
- 实现多态的理论基础:函数指针做函数参数
是否可类的每个成员函数声明为虚函数,为什么?
处于效率考虑,不能,通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用函数,而普通成员函数是在编译时就确定了调用的函数,在效率上,虚函数的效率要低很多。
https://blog.csdn.net/hsq1596753614/article/details/80249605
1引用和指针有什么区别?
- 定义一个指针变量编译器会为它分配内存,而引用不占用内存;
- 引用必须在定义时被初始化,指针不必;
- 不存在指向空值的引用,但存在指向空值的指针。
#include<stdio.h>
int main(void){
int *p;//int *p =NULL;
printf("%d\n",sizeof(p));
}
函数传递中值传递、地址传递、引用传递有什么区别?
- 值传递:(进入被调用函数后)会为形参重新分配内存空间,将实参的值拷贝给形参,形参的值不会影响实参的值,函数调用结束后形参被释放;
- 引用传递:不会为形参重新分配内存空间,形参只是实参的别名,形参的改变会影响实参的值,函数调用结束后形参不会被释放;
- 地址传递:形参为指针变量,将实参的地址传递给(被调用函数)函数,可以在函数中改变实参的值,调用时为形参指针变量分配内存,结束时释放指针变量。
static关键字有什么用?(C++中应用?)
- 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时维持上次的值;
- 在模块内的static全局变量可以被模块内所有函数调用,但不能被模块外其他函数访问;
- 在模块内的static函数只能被这一模块内的其他函数调用,这和函数的使用范围被限制在声明它的模块内;
- 在类中的static成员变量属于整个类拥有,对类的对象只有一份拷贝(?);
- 在类中的static成员函数属于整个类拥有,这个函数不接收this指针,因而只能访问static成员变量。
const关键字有什么作用?
- const int a;(int const a;)表示一个常量,定义const变量时,要它初始化,否则右系统分配,后面使用值无法修改;
- const int *b;(int const *b;)指针所指的数据不能被修改,但指针变量可被修改限定位置 *b;
- int *const c;指针所指的数据可被修改,但指针变量不可被修改,限定位置 *const
- const int *const b;二者同时指定为const,表示指针和指针所指内存数据均不可被修改;
- 在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
- 对于类的成员函数,若指定其为const类型,则表明是一个常函数,不能改变类的成员变量;
- 对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为左值;?
#include<stdio.h>
int main(){
int a = 10;
int b = 20;
printf("int const * -----\n");
const int *p = &a;//此时const限定int
printf("初始值: %d\n",*p);
printf("起始地址:%d\n",p);
// *p = 20;//指针变量指向地址内值不能改
// printf("%d",p);
p = &b; //指针变量本身地址可改变
printf("改变指针地址:%d\n",p);
printf("变指针地址的值:%d\n",*p);
printf("int *const ------------\n");
int *const q = &a;
printf("初始值:%d\n",*q);
printf("起始地址:%d\n",q);
// q = &b;//无法修改指针变量
*q= b;
printf("%d\n",q);
printf("%d\n",*q);
printf("cosnt int *const-----\n");
const int *const c = &a;
printf("起始地址:%d\n",c);
printf("初始值:%d\n",*c);
// *c = 30;//无法修改指针指向内存数据
// c = &b; //无法修改指针变量
// printf("c:%d\n",*c);
return 0;
}
/*
int const * -----
初始值: 10
起始地址:6487524
改变指针地址:6487520
变指针地址的值:20
int *const ------------
初始值:10
起始地址:6487524
6487524
20
cosnt int *const-----
起始地址:6487524
初始值:20
*/
小结:const关键词修饰后分为常量、常指针、指向常整形的常指针
- const关键词,看const 限定位置,const int *a;与int const *a;const限定的*a作用一样,表示指针变量指向的内存空间的值不能被改变,但指针变量本身地址可修改;
- int *const b;const 被*限定为常指针,常指针(指针变量不能被修改,但是它所指向内存空间可以被修改);
- const int *const b;被const双重限定,指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)
疑问:指针变量地址被修改后指向空间变了,即*p的值也发生改变。
链表和数组区别在哪?(队列存储方式,顺序存储和链式存储)
- 链表和数组都可叫线性表,数组又叫顺序表,区别在于,顺序表是在内存中开辟一段连续的空间来存储数据,而链表是靠指针来连接多块不连续的空间,在逻辑上形成一片连续的空间来存储数据;
- 数组要求空间连续,占用总空间小,链表不要求空间连续,占用总空间大;
- 数组方便排序和查找,但删除和插入较慢;链表方便删除和插入,范查找较慢,不方便排序;
编写能直接实现strlen()函数功能的代码
int strlen(char *str){
for(int i=0;str[i] !=’\0’;I++)
return i;
}
编写能直接实现strstr()函数功能的代码
进程和线程的差别
- 线程是指进程内的一个执行单元,也是进程内的可调度实体。
- 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位;
- 并发性:不仅进程之间可以并发执行,通一个进程的多个线程也可并发执行;
- 拥有资源:进程是拥有资源的一个独立单元,线程不拥有系统资源但可以访问隶属于进程的资源;
- 系统开销:在创建或撤销进程时,系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤销线程时的开销;
关于const关键
- char* const p;//常量指针,p的地址不可以修改
- char const* p;//指向常量的指针,指向内容不可以改
- Const char *p;//同char const *p;
- Const char *const p;//内容和地址都不可改变
memset与memcpy、strcpy区别
- memset用来对一段内存空间内全部设置为某个字符,一般用在对定义的字符串进行初始化为指定值;
- memcpy用在内存拷贝,可以用来拷贝任何数据类型的对象,可以指定拷贝的数据长度;
- strcpy只能拷贝字符串,遇到’\0’就结束拷贝;
析构函数有那些特点?
- 析构函数是特殊的类成员函数,它没有返回类型、没有参数、没有重载;
- 析构函数不能手动调用,只是在类对象生命周期结束的时候,由系统自动调用释放在构造函数中分配的资源;
虚函数有什么作用?
- 虚函数是使子类可以用同名的函数对父类函数进行覆盖,并且在通过父类指针调用时,如果有覆盖则自动调用子类覆盖函数,如果没有覆盖则调用父类中的函数,从而实现灵活扩展和多态性;
- 如果是纯虚函数,则是纯粹是为了在子类覆盖时有个统一的命名而已,子类必须覆盖纯虚函数,否则子类也是抽象类;
- 含有纯虚函数的类称为抽象类,不能实例化对象,主要用作接口类;
虚析构函数有什么作用
- 析构函数的工作方式是最底层的派生类的析构函数最先被调用,然后调用每一个基类的析构函数;
- 在C++中,当一个派生类对象通过使用一个基类指针删除,而这个基类有一个非虚的析构函数,则可能导致运行时派生类不能被销毁,然而基类部分很有可能被销毁,这导致“部分析构”现象,造成内存泄露;
- 给基类一个虚析构函数,删除一个派生类对象的时候就将销毁整个对象,包括父类和全部的派生类部分;
分别给出bool、int、float、指针变量与零值比较的if语句
- bool型变量:if(!var)
- int型变量: if(var == 0)
- float型变量:
- const float EPSINON =0.000001;
- If((x >= -EPSINON )&&(x<=EPSINON ))
- 指针变量:if(var == NULL)
一下为Windows NT下32位C++程序,请计算sizeof的值
void Func(char str[100]){
sizeof(str)=? //答案4,32编译器跑100
}
void *p=malloc(100);
sizeof(p)=??//4
int a[100];
sizeof(p)=?//400
char *p=”aaaaaaa”;
sizoef(p)=? //4
写一个函数返回1+2+3+。。。+n的值
int sum(int n){
return (1+n)*n/2;或者return ((long)1+n)*n/2;
}
深度变量二叉树
C++中的inline内联函数与普通函数的区别
所谓“内联函数”就是很简单的函数内嵌到调用它的程序代码中,这样做目的是节约原本函数用时的时空开销,但作为内联函数,函数体必须十分简单,不能含有循环、条件、选择等复杂的结构。
内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的,而且内联函数是真正的函数,只是在需要用到的时候内联函数想宏一样展开,所以取消了函数的参数压栈,减小了调用的开销,可以像调用函数一样调用内联函数,而不必担心会产生处理宏的一些问题。
可以用inline来定义内联函数,不过,任何在类的说明部分定义的函数都会被自动的认为是内联函数,内联函数必须是和函数体声明在一起才有效。
C++重写、重载、重定义的区别?
成员函数重载特征:相同的范围,在同一个类、函数名字相同、参数不同
重写(覆盖)是指派生类函数覆盖基类函数,特征是:
不同的范围,分别位于基类和派生类中,函数名字相同、参数相同、基类函数必须有virtual关键字
重定义(隐藏)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
如果派生类的函数和基类的函数同名,但是参数不同,此时不管有无virtual,基类函数被隐藏。
如果派生类的函数与基类的函数同名,且参数相同,但是基类函数没有virtual关键字,此时基类函数被隐藏。
一个数据成员是否可以既是const又是static,如果不行,为什么?
一个数据成员可以既是const又是static,表示为静态常量;
常量一般在构造函数后初始化;
静态成员一般在类外初始化;
静态常量在类外初始化,但要在类外初始化的同时声明const。
构造函数与析构函数异同点
构造函数特点如下:
构造函数名字必须与类名相同
构造函数可以有任意类型的参数,但不能返回类型;
定义对象时,编译系统会自动调用构造函数;
构造函数是特殊的成员函数,函数体可以在类体内也可在类体外;
构造函数被声明为公有函数,但它不能像其他成员函数那样被显式调用,它是在定义对象的同时被调用的;
析构函数特点如下:
析构函数的名字必须与类名相同,但它前面必须加一个波浪号;
析构函数没有参数,也没有返回值,而且不能被重载,因此在一个类中只能有一个析构函数;
当撤销对象时,编译系统会自动调用析构函数;
析构函数可以是virtual,而构造函数不能是虚函数;
自动调用复制构造函数的几种情况
复制构造函数的功能是用一个已知对象来初始化另一个同类的对象。复制构造函数其实也是类的构造函数,与类名相同,有且只有一个参数,是该类对象的引用,每个类必须有一个复制构造函数,如果定义类的时候没有编写,编译器编译会自动生成一个复制构造函数。
复制构造函数在三种情况会被自动调用:
当类的一个对象去初始化该类的另一个对象时;
如果函数的形参是类的对象,调用函数进行形参和实参结合时;
如果函数的返回值是类对象,函数调用完成返回时;
类型转换构造函数是什么?举个例子
类型转换构造函数就是自动调用类型匹配的构造函数,自动将基本数据类型转换成对象,
简述C++异常处理方式
一个典型C++异常处理包含以下几个步骤
程序执行是发生错误;以一个异常对象(最简单是一个整数)记录错误的原因及相关信息;
程序监测到这个错误(读取异常对象);程序决定如何处理错误;
进行错误处理,并在此后恢复/终止程序的执行;
成员函数和友员函数的区别
成员函数是类定义的一部分,通过特定的对象来调用。成员函数既是可以隐式访问调用对象的成员。而无须使用成员操作符;
友员函数不是类的组成部分,因此被称为直接函数调用。友元函数不能隐式访问类成员,而必须将成员操作符用于作为参数传递的对象。
C++中那些运算符不可以重载?
.、?:、sizeof、::、*
26、如何重载前++ 和后++运算符?
前++不带参数,后++带一个int型参数以示区分。
请说出STL标准模板版库中的几个常用类
函数模板与函数重载的异同?
函数的重载是指定义了几个名字相同,但参数的类型或参数的个数不同的函数;
模板函数是指的几个函数的具体算法相同,而参数类型不同的函数;
模板函数可以减少重载函数,但也可能引发错误。
C++中explicit关键字有什么作用?
explicit和构造函数一起使用,explicit指明构造函数只能显式使用,目的是为了防止不必要的隐式调用类型转换构造函数。
C++中restrict关键字有什么作用?
Restrict是用来优化的;restrict只能修饰指针,restrict修饰的指针是能够访问所指区域的唯一入口,限制多个指针指向同一地址;
面向对象的三大特征是什么?
面向对象的三个基本特征:封装、继承、多态。
什么是封装?
- 封装是面向对象的特征之一,是对象和类概念的主要特性;
- 封装,也就是把客观事物封装成抽象类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏;
- 在C++中类中成员的属性有:public、protected、private,这三个属性的访问权限一次降低;
什么是继承?
- 继承是指:可以使用现有类的所有功能,并在无须重新编写原来的类的情况下对这些功能进行扩展;
- 通过继承创建的类称为“子类”或“派生类”;
- 被继承的类称为“基类”、“父类”或者”“超类”;
- 在某些OOP语言中,一个子类可以继承多个基类,但是一般清况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现;
C++中类中成员的属性有:public、protected、private来修饰继承特性,进行属性访问控制;
什么是多态?
多态性:允许将父类对象设置为和一个或更多的它的子类相等的技术,赋值之后,父对象可以根据当前赋值给它的子对象的特性以不同方式运作,简单地说,允许将子类类型的指针赋值给父类类型的指针;
实现多态的两种方式:覆盖、重载;
覆盖:子类重新定义父类的虚函数;
重载:允许存在多个同名函数,而这些函数的参数表不同;
类和对象的区别?
类与对象的区别,如人类与张三的区别,它们是一般与个体,抽象与具体,集体与个体的区别。
C++中namespace是什么?
Namespace命名空间
什么是可重入和不可重入函数?
什么是可重入性?
可重入函数可以由多个任务并发使用,而不必担心数据错误,相反,不可重入函数不能有多个任务共享,除非能确保函数的互斥。可重入函数可以在任意时刻被中断,稍后继续运行,不会丢失数据,可重入函数要么使用本地变量,要么使用全局变量时保护自己的数据;
可重入函数
不可持续的调用持有静态数据;不返回指向静态数据的指针,所有数据都是有函数的调用者提供;使用本地数据,或者通过制作全局数据的本地拷贝和保护全局数据;
如果必须访问全局变量,记住利用互斥信号量老保护全局变量;
不可重入函数
- 函数中使用了静态变量,无论是全局静态变量还是全局变量;
- 函数返回静态变量;函数中调用了不可重入函数;
- 函数体内使用了不可重入函数;
- 函数体内使用了静态的数据结构;
- 函数体内调用了malloc()或free()函数;
- 函数体内调用了其他标准I/O函数;
总之,如果一个函数在重入条件使用了未受保护的共享资源,那么就是不可重入的;