2018-10-10 开立医疗C++笔试面经

开立医疗C++开发

一、双层for循环优化

  1. 将循环次数大的放到外层循环

  2. 将双层循环变成单层循环

  3. 原题目是将一个二维数组转换为一维数组,可以考虑指针:

#include
using namespace std;
int main() {
    int a[3][3] = { 1,2,3,4,5,6,7,8,9 };
    //int *b = (int*)a;
    //int *b = a[0];
    int *b = &a[0][0];
    for (int i = 0; i < 9; i++) {
       cout << *(b++) << endl;
     } 
return 0;
}

这里所使用的三种方法都是将一个指针指向数组a的首地址,在内存中a是连续存储的,内存分布如下:

2018-10-10 开立医疗C++笔试面经_第1张图片
image

可以看到,a数组在内存中按照行的顺序存储,后面的行接着前面的行顺序存储,地址从低到高。

补充:查看内存(必须是调试状态下)
如果查看a的内存分布,可以在内存那里输入 &a,回车即可。

2018-10-10 开立医疗C++笔试面经_第2张图片
image

二、设计模式 适配器模式

适配器模式是将一个类的接口转换成客户希望的另外一个接口,身边很多东西都是适用于适配器模式的,笔记本的电源(也叫电源适配器),是将220V的交流电转换为笔记本电脑所需要的12V(电流先忽略),笔记本电脑的各种接口,VGA转Hdml,USB-TypeA 转 USB-TypeC,亦或者你在香港买了个手机,充电器是你生活中没见过的三孔插座通过一个转换头转换为国内常用的插头,很多例子都能很形象的解释这个设计模式。适配器模式(有时候也称包装样式或者包装)将一个类的接口适配成用户所期待的。一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。

三、字符串的复制和赋值

字符串复制
  1. 数组复制
void strcpy(char *des,char *src)
{
    int i;
    for(i=0;src[i]!='\0';i++)
    {
        des[i] = src[i];
    }
    des[i] = '\0';
}
  1. 指针复制
void strcpy(char *des,char *src)
{
    while(*des++ = *src++) ;
}
字符串赋值
//1. 定义时初始化赋值
char buf[20]="hello world!";
char *str="hello world!"

//2. 先定义再初始化
char buf[20];   char*str;
buf = "I love china";   //错误,数组名是常量,不能被赋值    
strcpy (buf, "I love china");    
str = "I love china";          
strcpy(str, "I love china");  //段错误,没有给str指针分配内存

//3. 上述最后一种方法的改正
str = (char *)malloc(sizeof(char)*20);
strcpy(str, "I love china");

四、数据库更新数据

update scoretable set score = score + 10 where name = 'lili'

五、多态

  1. 编译时的多态性:重载
    在一个类中,方法名相同而参数(顺序,个数和类型)不同。

  2. 运行时的多态性:虚函数
    直到系统运行时,才根据实际情况决定实现何种操作。虚函数的重写(重写:函数三要素(函数名、函数参数、函数返回类型)完全一样)即是多态的体现。

    补充:重定义,也叫做隐藏,子类重定义父类中有相同名称的非虚函数(参数可以不同)。如果一个类,存在和父类相同的函数,那么这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用时不能成功的。

六、虚函数的实现原理

此处贴一下讲的很好的博文:
https://www.cnblogs.com/malecrab/p/5572730.html
http://www.cnblogs.com/malecrab/p/5572119.html
虚函数的动态绑定实现机制:
只有通过基类的引用或者指针调用虚函数时,才能发生动态绑定,如果使用对象来操作虚函数的话,仍然会采用静态绑定的方式。因为引用或者指针既可以指向基类对象,也可以指向派生类对象的基类部分。绝对不要重新定义一个继承而来的virtual函数的缺省参数值,因为缺省参数值都是静态绑定(为了执行效率),而virtual函数却是动态绑定。

七、内存分区

https://www.cnblogs.com/madonion/articles/2269195.html
分为五大块:栈,堆,全局区,常量区和代码区

  1. 栈区
    由系统进行内存的管理,主要存放函数的参数以及局部变量。栈区由系统进行内存管理,在函数完成执行,系统自行释放栈区内存,不需要用户管理。整个程序的栈区的大小可以在编译器中由用户自行设定,默认的栈区大小为3M。
  2. 全局/静态区
    初始化的全局变量和静态变量是在一起的。未初始化的全局变量和静态变量是在相邻的空间中。全局变量和静态全局变量的存储方式是一致的,但是其区别在于,全局变量在整个源代码中都可以使用,而静态全局变量只能在当前文件中有效。比如我们的一个程序有5个文件,那么某个文件中申请了静态全局变量,这个静态全局变量只能在当前文件中使用,其他四个文件均不可以使用。而某个文件中申请了全局变量,那么其他四个文件中都可以使用该全局变量(只需要通过关键字extern申明一下就可以使用了)。事实上static改变了变量的作用范围。
  3. 字符串常量区
    存放字符串常量,程序结束后,由系统进行释放。比如我们定义const char * p = “Hello World”; 这里的“Hello World”就是在字符串常量中,最终系统会自动释放。
  4. 代码区:存放程序体的二进制代码。比如我们写的函数,都是在代码区的。
  5. 堆区:由用户手动申请,手动释放。在C中使用malloc,在C++中使用new(当然C++中也可以使用malloc)。
    new操作符本质上还是使用了malloc进行内存的申请
    1)malloc是C语言中的函数,而new是C++中的操作符。
    2)malloc申请之后返回的类型是void*,而new返回的指针带有类型。
    3)malloc只负责内存的分配而不会调用类的构造函数,而new不仅会分配内存,而且会自动调用类的构造函数。

八、堆和栈的区别

  1. 管理方式:栈是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生内存泄漏。
  2. 空间大小:堆内存比栈大得多。
  3. 能否产生碎片:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因 为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出。
  4. 生长方向:堆生长方向是向上的,也就是向着内存地址增加的方向;栈的生长方向是向下的,是向着内存地址减小的方向增长。
  5. 分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
  6. 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内 存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到 足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

九、栈溢出

当我们定义的数据所需要占用的内存超过了栈的大小时,就会发生栈溢出。
https://blog.csdn.net/dongtuoc/article/details/79132137

十、内存泄露和内存碎片

1. 内存泄漏

内存泄漏一般是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。如果没有及时释放,那么这一块内存便不能使用,从而造成内存泄漏。

2. 内存碎片

内存碎片一般是由于空闲的连续空间比要申请的空间小,导致这些小内存块不能被利用。
假设有一块100个单位的连续空闲内存空间,范围是0-99。从中申请一块内存,10个单位,那么申请出来的内存块就为0-9区间。继续申请一块内存,5个单位,第二块得到的内存块就应该为10~14区间。
如果你把第一块内存块释放,然后再申请一块20个单位的内存块。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。现在整个内存空间的状态是0-9空闲,10-14被占用,15-24被占用,25-99空闲。其中0-9就是一个内存碎片了。如果10-14一直被占用,而以后申请的空间都大于10个单位,那么0-9就永远用不上了,造成内存浪费。

十一、同步和异步

同步:在发出一个功能调用时,在没有得到结果之前,该调用就不返回。
异步。
异步:当一个异步过程调用发出后,调用者不会立刻得到结果。实际处理这个调用的部件是在调用发出后,通过状态、通知来通知调用者,或通过回调函数处理这个调用。异步的实现方式有两种:通过多进程和timmer。

十二、野指针

  1. 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
  2. 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。释放内存后必须把指针指向NULL,防止指针在后面不小心又被解引用了。具体参考博文:https://blog.csdn.net/dangercheng/article/details/12618161
  3. 指针超过了变量的作用范围。即在变量的作用范围之外使用了指向变量地址的指针。这一般发生在将调用函数中的局部变量的地址传出来引起的。局部变量的作用范围虽然已经结束,内存已经被释放,然而地址值仍是可用的,不过随时都可能被内存管理分配给其他变量。

十三、指针的使用有什么注意

  1. 函数的返回值不能是指向栈内存的指针或引用,因为栈内存在函数结束时会被释放.
  2. 在使用指针进行内存操作前记得要先给指针分配一个动态内存。
  3. 声明一个指针时最好初始化,指向NULL或者一块内存。

十四、形参和实参

概念
  1. 形参
    形参出现在函数定义的地方,多个形参之间以逗号分隔,形参规定了一个函数所接受数据的类型和数量。
    形参和函数体内部定义的变量统称为局部变量,仅在函数的作用域内可见,同时局部变量还会隐藏在外层作用域中同名的其他所有声明(局部变量和全局变量可以重名)
  2. 实参
    出现在函数调用的地方,实参的数量与类型与形参一样,实参用于初始化形参。
实参与形参值传递的方式
  1. 值传递
    在值传递过程中,实参和形参位于内存中两个不同地址中,参数传递的实质是将原函数中变量的值,复制到被调用函数形参所在的存储空间中,这个形参的地址空间在函数执行完毕后,会被回收掉。整个被调用函数对形参的操作,只影响形参对应的地址空间,不影响原来函数中的变量的值,因为这两个不是同一个存储空间。
  2. 地址传递(指针传递)
    这种参数传递方式中,实参是变量的地址,形参是指针类型的变量,在函数中对指针变量的操作,就是对实参(变量地址)所对应的变量的操作,,函数调用结束后,原函数中的变量的值将会发生改变。
  3. 引用传递
    这种参数传递方式中,形参是引用类型变量,其实就是实参的一个别名,在被调用函数中,对引用变量的所有操作等价于对实参的操作,这样,整个函数执行完毕后,原先的实参的值将会发生改变。
    如果在实参前加上const关键字修饰,则引用传递可以不改变实参的值,既达到了传值的目的,提高了效率,还保证了原实参不会被修改。
    https://blog.csdn.net/u012677715/article/details/73825856
    https://www.cnblogs.com/kane0526/p/3913284.html
    https://www.cnblogs.com/tanjuntao/p/8678927.html

十五、时间复杂度和空间复杂度

  1. 空间复杂度,该程序的运行所需内存的大小
    计算公式:S(n) = O(f(n)),n为问题的规模,f(n)为语句关于n所占存储空间的函数。
  2. 时间复杂度,运行程序所需要的时间
    如果一个算法的执行次数是 T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数 f(n),此时算法的时间复杂度就是 O(f(n))。
    https://www.jianshu.com/p/f4cca5ce055a
    https://blog.csdn.net/qq_30891667/article/details/72236507

你可能感兴趣的:(2018-10-10 开立医疗C++笔试面经)