后端开发面试准备-字节跳动-C++

匿名 (未验证) 提交于 2019-12-02 23:55:01

C++

1.内存泄漏

用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。即所谓内存泄漏。
注意:内存泄漏是指堆内存的泄漏。

简单的说就是申请了一块内存空间,使用完毕后没有释放掉。
它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。//所以这是windows长时间不关机不好的原因之一?
由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。


2.野指针

“野指针”不是NULL指针,是指向“垃圾”内存的指针。野地。
野指针的成因主要有两种:

  • 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存
  • 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块

2.1悬挂指针

int *p = nullptr; if (p == nullptr) {    	int c = 100; 	p = &c; } 

退出if的范围之后,c变量不再存在,p指向的地址被操作系统回收了。指针被悬挂了,后续如果再被使用就会有问题。

int *p = nullptr; {    	int *q = new int 	p = q; 	delete q; } 

变量p指向被释放了,指针被悬挂了


3.memcpy-内存重叠

存在dst头部地址出现在src区域内,也就是dst前面覆盖到了src的后面,就从后往前复制。
因为如果从前往后复制的话,src的后面的值再被复制到dst前就被改写成了src的前面复制过的值了。
再考虑如果dst后面覆盖了src的前面这种情况,因为是从src复制到dst,那就从前往后复制,因为被覆盖的区域是不影响后续的复制的。
再考虑边界情况,如果dst的位置和src的位置一样,那么正常从前往后复制,dst的位置在src+len,也就是小于等于dst的位置,同理。

以下内容更详细,来自这里
内存重叠问题是指目的地址的内存空间的首地址,包含在源内存空间中,这两段内存空间有了交集,因而在使用memcpy进行内存复制操作时,这段重叠的内存空间会被破坏.这种情况在应用程序级代码中一般不会出现的,而在驱动或内核级代码中要十分小心,尽量使用memmove函数.

1.不要破坏传进来的形参,定义新的临时变量来操作
2.考虑指针的类型,不同类型的指针不能直接++赋值
3.overlap情况下需要从高地址处向前copy

void*Memcpy(void *dst, const void *src, size_t size);   int main(intargc, char *argv[]) {     char buf[100] = "abcdefghijk";     memcpy(buf+2, buf, 5);     //Memcpy(buf+2, buf, 5);     printf("%s\n", buf+2);            return 0; }   void*Memcpy(void *dst, const void *src, size_t size) {     char *psrc;     char *pdst;              if(NULL == dst || NULL == src)     {         return NULL;     }              if((src < dst) && (char *)src +size > (char *)dst) // 自后向前拷贝     {         psrc = (char *)src + size - 1;         pdst = (char *)dst + size - 1;         while(size--)         {             *pdst-- = *psrc--;         }     }     else     {         psrc = (char *)src;         pdst = (char *)dst;         while(size--)         {             *pdst++ = *psrc++;         }     }              return dst; } 

4.C中函数指针作用

1)提供调用的灵活性。设计好了一个函数框架,但是设计初期并不知道自己的函数会被如何使用。比如C的”stdlib”中声明的qsort函数,用来对数值进行排序。显然,顺序还是降序,元素谁大谁小这些问题,库程序员在编写qsort的时候不可能决定。这些问题是要在用户调用这个函数的时候才能够决定。那边qsort如何保证通用性和灵活性呢?采用的办法是让函数的使用者来制定排序规则。于是调用者应该自己设计comparator函数,传给qsort函数。这就在程序设计初期保证了灵活性。尽管使用函数指针使得程序有些难懂,但是这样的牺牲还是值得的。

2)提供封装性能。有点面向对象编程的特点。C实现面向对象编程的一种途径,大致可以实行 面向对象编程中的多态性和回调函数,

比如 设计一个栈结构

typedef struct _c_stack{                int base_size;                int point;                int * base;                int size;                int  (*pop)(struct _c_stack *);                int  (*push)(int,struct _c_stack *);                int  (*get_top)(struct _c_stack); }c_stack; 

在初始化完之后,用户调用这个结构体上的pop函数,只需要s.pop(&s)即可。即使这个时候,工程内部有另外一个函数名字也叫pop,他们之间是不会发生名字上的冲突的。
原因很简单,因为结构体中的函数指针指向的函数名字可能是

int ugly_stupid_no_one_will_use_this_name_pop(c_stack *) 

只是stack的用户是不知道他在调用s.pop(&s),实际上起作用的是这样一个有着冗长名字的函数。
函数指针这种避免命名冲突上的额外好处对于一些库函数的编写者是很有意义的,因为库可能被很多的用户在许多不同的环境下使用,这样就能有效的避免冲突而保证库的可用性。


5.隐式类型转换 及其问题

以下内容摘自这里

5.1什么是隐式转换?

众所周知,C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的。
所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。很多时候用户可能都不知道进行了哪些转换。

5.2为什么要进行隐式转换?

  • C++面向对象的多态特性,通过父类的类型实现对子类的封装。
  • 数值和布尔类型的转换,整数和浮点数的转换等。
  • C++是一门强类型语言,类型的检查是非常严格的。如果没有类型的隐式转换,这将给程序开发者带来很多的不便。

5.3隐式转换原则

  • 基本数据类型的转换以取值范围的作为转换基础(保证精度不丢失)。 隐式转换发生在从小->大的转换中。比如从char转换为int。从int到long。
  • 自定义对象 子类对象可以隐式的转换为父类对象。

5.4隐式转换发生条件

  • 混合类型的算术运算表达式中
int a = 3; double b = 4.5; a + b; // a将会被自动转换为double类型,转换的结果和b进行加法操作 
  • 不同类型的赋值操作
int a = true; (bool类型被转换为int类型) int * ptr = null;(null被转换为int*类型) 
  • 函数参数传值
void func(double a); func(1); // 1被隐式的转换为double类型1.0 
  • 函数返回值
double add(int a, int b) {     return a + b; } //运算的结果会被隐式的转换为double类型返回 

  不满足该原则,隐式转换是不能发生的。    当然这个时候就可以使用与之相对于的显式类型转换(又称强制类型转换),使用方法如下:    double a = 2.0;    int b = (int)a;   使用强制类型转换会导致精度的损失,因此使用时务必确保你已经拥有足够的把握。 

5.5隐式转换带来的风险

隐式转换的风险一般存在于自定义的类构造函数中。

class String { public:     String ( const char* p ); // 用C风格的字符串p作为初始化值     //… }   String s1 = “hello”; //OK 隐式转换,等价于String s1 = String(”hello”) 

但是有的时候可能会不需要这种隐式转换

例1

class String { public:     String ( int n ); //本意是预先分配n个字节给字符串     String ( const char* p ); // 用C风格的字符串p作为初始化值       //… } 

下面两种写法比较正常:

    String s2 ( 10 );   //OK 分配10个字节的空字符串     String s3 = String ( 10 ); //OK 分配10个字节的空字符串 

下面两种写法就比较疑惑了

String s4 = 10; //编译通过,也是分配10个字节的空字符串 String s5 = ‘a’; //编译通过,分配int(‘a’)个字节的空字符串 

s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。

例2

class Test { public:   Test(int a);   bool isSame(Test other)   {     return m_val == other.m_val;   }   privateint m_val; } Test a(10); If(a.isSame(10)) //该语句将返回true  

本来用于两个Test对象的比较,竟然和int类型相等了。
这里就是由于发生了隐式转换,实际比较的是一个临时的Test对象。

5.6 explicit 禁止隐式类型转换

在构造函数声明的时候加上explicit关键字,能够禁止隐式转换。

class Test { explicit Test(int a); ……   } 加上该关键字以后,如下的操作是合法的: Test(10);  如下的操作就变成非法的了: Test aa = 10;   这样就可以有效的防止隐式转换的发生,从而能够对程序进行精确控制,达到提高品质的目的。 

THE END


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!