C_points

1.C和C++的struct有什么不同

C和C++的struct的主要区别是C中的struct不可以包含成员函数,而C++中的struct可以。C++中struct和class主要区别在于默认权限不同,struct默认为public,class默认为private.

2.const和define的比较?

const常量有数据类型,而宏常量没有数据类型。编译器可以对const常量进行类型安全检查,但对define常量只能进行字符替换。GDB工具可以对const常量进行调试,但不能对宏常量调试。

3.引用和指针的区别?

(1)引用被创建的同时必须初始化,指针可以在任何时候被初始化。
(2)不能由NULL引用,引用必须与合法的存储单元关联,指针可以是NULL
(3)一旦引用被初始化,就不能改变引用的关系,指针可以随时改变所指的对象。
(4)指针有自己的一块空间,而引用只是一个别名。

4.数组和链表的区别?

数组:数据顺序存储,固定大小;链表:数据可以随机存储,大小可动态改变。
5.float 和零值比较
const float ESP = 0.00000001f;
float x = 0.0f;
if (x >= -ESP && x <= ESP) printf(“yes\n”);

6.逻辑短路问题:

int a=5,b=6,c=7,d=8,m=2,n=2;
(m=a<b)||(n=c>d);
printf("%d,%d",m,n); ->m=1,n=2;(m=a<b)为真,不会执行||后面的语句;
(n=c>d)&&(m=a<b);
printf("%d,%d",m,n); ->m=2,n=0;(n=c>d)为假,不会执行&&后面的语句;

7.运算符优先级:

伪运算符((),[],->)优先级最高,单目运算符第二;
算术运算符>左移右移>比较运算符>位运算符>逻辑运算符;
赋值运算符优先级倒数第二,逗号最低;
自右向左结合的只有单目运算符、三目运算符、赋值运算符。

8.strcpy()和memcpy()比较?

char* strcpy(char* dest, const char* src);-字符串拷贝,会拷贝'\0'
char * strcpy(char * dest, const char * src) // 实现src到dest的复制
{
  if ((src == NULL) || (dest == NULL)) //判断参数src和dest的有效性
     return NULL;
  char *strdest = dest;        //保存目标字符串的首地址
  while ((*strDest++ = *strSrc++)!='\0'); //把src字符串的内容复制到dest下
  return strdest;
}
void *memcpy( void *dest, const void *src, size_t count );-内存拷贝
void *memcpy(void *memTo, const void *memFrom, size_t size)
{
  if((memTo == NULL) || (memFrom == NULL)) //memTo和memFrom必须有效
         return NULL;
  char *tempFrom = (char *)memFrom;             //保存memFrom首地址
  char *tempTo = (char *)memTo;                  //保存memTo首地址      
  while(size -- > 0)                //循环size次,复制memFrom的值到memTo中
         *tempTo++ = *tempFrom++ ;  
  return memTo;
}

strcpy和memcpy主要有以下3方面的区别。
(1)、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
(2)、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
(3)、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

9.函数返回值:

若省略返回值类型,编译器默认返回int型,不同编译器处理方式不同。

10.char *str = “abc”;

str[1] = ‘d’;//错误,str是指向常量的指针,常量不能被修改。
char str[] = “abc”; str[1]=‘d’;//可以

11.static和const

static:
(1)函数内部的变量加上static后(放在静态变量区),生命周期被延长,作用域依然在本函数内,但是内存分配和初始化只执行一次,下次调用仍维持上一次的值。
(2)全局变量 int b加上static后,只能够在本文件内被使用,生命周期还是整个程序运行过程,但是作用域被限制在声明这个变量的文件内。
(3)static函数只能在自己定义的文件中使用
(4)在类中的static成员变量属于整个类的,对于类的所有对象只能维持一份拷贝
(5)在类中的static成员函数属于整个类的,这个函数是不接受this指针的,只能访问类的static成员。
const:定义的时候必须初始化
const的用法(绝对不能说是常数)
1)在定义的时候必须进行初始化
2)指针可以是const 指针,也可以是指向const对象的指针
3)定义为const的形参,即在函数内部是不能被修改的
4)类的成员函数可以被声明为常成员函数,不能修改类的成员变量
5)类的成员函数可以返回的是常对象,即被const声明的对象
6)类的成员变量是常成员变量不能在声明时初始化,必须在构造函数的列表里进行初始化
char ch = ‘a’;
char ch2 = ‘n’;
const char * p1 = &ch;//定义一个指向字符常量的指针, *ptr的值是const char,不能修改。 p1 = &ch2; p1的指向可以改变。*p1='c’是不可以的。
char const * p2 = &ch;//和const char *等价
char * const p3 = &ch;//一个指向字符的指针常数,即const指针,p3 = &ch2 错误,p3的指向不能改变。 *p3='c’是可以的。

12.字节对齐:

为什么要字节对齐?CPU访问数据的效率问题。
A.结构体的大小为最大成员的整数倍。
B.成员首地址的偏移量为其类型大小整数倍。

13.static作用

1.全局静态变量:
在全局变量前加上关键字static,全局变量就变成了全局静态变量。静态存储区,在整个程序运行期间一直存在。未初始化的全局静态变量会被自动初始化为0,作用域:全局静态变量在声明他的文件之外是不可见的。

2.局部静态变量:
内存中的位置:静态存储区。
初始化:未初始化的全局静态变量会被自动初始化为0
作用域:局部作用域当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变;

3.静态函数:
在函数返回类型前加static,函数就定义为静态函数。静态函数只是在声明他的文件当中可见,不能被其他文件所用。

4.类的静态成员:
静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用。

5.类的静态函数:
静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。

14.C和C++的区别:

1.设计思想上:
C++是面向对象的语言,C是面向过程的结构化编程语言
2.语法上:
C++具有封装,继承和多态三种特性,相比C而言,增加了许多类型安全的功能,C++还支持范式编程,比如模板类,函数模板等。

15.野指针

野指针是指向一个已删除的对象或者未申请访问受限内存区域的指针。

16.智能指针:

实际上智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,释放资源,避免申请的空间在函数结束时忘记释放,造成内存泄漏的情况。
1.auto_ptr:采用所有权模式。

auto_ptr< string> p1 (new string ("I reigned lonely as a cloud.));

auto_ptr<string> p2;

p2 = p1; //auto_ptr不会报错.

此时不会报错,p2剥夺了p1的所有权,但是当程序运行时访问p1将会报错。所以auto_ptr的缺点是:存在潜在的内存崩溃问题!

2.unique_ptr:
独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。

采用所有权模式,还是上面那个例子

unique_ptr<string> p3 (new string ("auto"));           //#4

unique_ptr<string> p4;                           //#5

p4 = p3;//此时会报错!!


编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。

3.shared_ptr
shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。
从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。
4.weak_ptr
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。

17.智能指针有没有内存泄漏的情况

当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。

为了解决循环引用导致的内存泄漏,引入了weak_ptr弱指针,weak_ptr的构造函数不会修改引用计数的值,从而不会对对象的内存进行管理,其类似一个普通指针,但不指向引用计数的共享内存,但是其可以检测到所管理的对象是否已经被释放,从而避免非法访问。

18.析构函数必须是虚函数,但默认析构函数不是虚函数

将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。

C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。

构造函数不能是虚函数:
首先需要了解 vptr指针和虚函数表的概念,以及这两者的关联。

vptr指针指向虚函数表,执行虚函数的时候,会调用vptr指针指向的虚函数的地址。

当定义一个对象的时候,首先会分配对象内存空间,然后调用构造函数来初始化对象。vptr变量是在构造函数中进行初始化的。又因为执行虚函数需要通过vptr指针来调用。如果可以定义构造函数为虚函数,那么就会陷入先有鸡还是先有蛋的循环讨论中。

19.函数指针

函数指针是指向函数的指针变量。
用途:调用函数和做函数的参数,比如回调函数。
char * fun(char *p) {…} //函数fun
char *(*pf) (char *p); //函数指针pf
pf = fun; //函数指针pf指向函数fun
pf§; //通过函数指针pf调用函数fun

20.fork函数

创建一个新的进程。
成功调用fork( )会创建一个新的进程,它几乎与调用fork( )的进程一模一样,这两个进程都会继续运行。在子进程中,成功的fork( )调用会返回0。在父进程中fork( )返回子进程的pid。如果出现错误,fork( )返回一个负值。

现代的Unix系统采取了更多的优化,例如Linux,采用了写时复制的方法,而不是对父进程空间进程整体复制。

21.静态函数和虚函数

静态函数在编译的时候就已经确定运行时间,虚函数在运行时动态绑定。虚函数采用了虚函数表机制,调用的时候回增加一次内存开销。

22.重载和覆盖

重载:两个函数名相同,但是参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中。

重写:子类继承了父类,父类中的函数是虚函数,在子类中重新定义了这个虚函数,这种情况是重写。

23.虚函数和多态

多态分为静态多态和动态多态。静态多态主要是重载,在编译的时候已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。

虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,指向虚函数表,表中存放了虚函数的地址,实际的虚函数在代码段中。当子类继承了父类的时候也会继承其虚函数表,当子类重写弗雷中虚函数的时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。
虚函数指针属于对象。

24.区别:

const char * arr = “123”;
//字符串123保存在常量区,const本来是修饰arr指向的值不能通过arr去修改,但是字符串“123”在常量区,本来就不能改变,所以加不加const效果都一样

char * brr = “123”;

//字符串123保存在常量区,这个arr指针指向的是同一个位置,同样不能通过brr去修改"123"的值

const char crr[] = “123”;

//这里123本来是在栈上的,但是编译器可能会做某些优化,将其放到常量区

char drr[] = “123”;

//字符串123保存在栈区,可以通过drr去修改

25.include头文件的顺序以及双引号“”和尖括号<>的区别?

头文件的顺序:
双引号和尖括号的区别:编译器预处理阶段查找头文件的路径不一样。
对于双引号的头文件,查找头文件路径的顺序为:当前头文件目录,编译器设置的头文件路径,系统变量
对于尖括号的头文件,查找头文件路径的顺序为:编译器设置的头文件路径,系统变量

26.malloc的原理:

为了减少内存碎片和系统调用的开销,malloc其采用内存池的方式,先申请大块内存作为堆区,然后将堆区分为多个内存块,以块作为内存管理的单位。当用户申请内存时,直接从堆区分配一块合适的空闲块。

27.C++的内存管理:

低地址->高地址
代码段:包括只读存储区(字符串常量)和文本区(程序的机器代码)
数据段:存储程序中已初始化的全局变量和静态变量
bss段:存储未初始化的全局变量和静态变量,以及所有被初始化为0的全局变量和静态变量
堆区:调用new/malloc函数时在堆区动态分配的内存,同时需要调用delete/free来手动释放申请的内存
栈区:使用栈空间存储函数的返回地址、参数、局部变量、返回值。

28.如何判断内存泄漏?

内存泄漏通常是由于调用了malloc/new等内存申请操作,但缺少了对应的free/delete。为了判断内存是否泄漏,我们一方面可以使用linux环境下的内存泄漏检查工具Valgrind mtrace,另一方面我们在写代码时可以添加内存申请、释放的统计功能,统计当前申请和释放的内存是否一致。

内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。
(1)堆内存泄漏(heap leak):程序运行中通过new/malloc申请了一块内存,但是完成后没有通过delete/free释放。
(2)系统资源泄漏(resource leak):程序使用系统分配的资源比如:bitmap,handle,socket等没有使用相应的函数释放掉,导致系统资源的浪费。
(3)没有将基类的析构函数定义为虚函数。当基类指针指向子类对象时,如果基类的析构函数不是虚函数,那么子类的析构函数不会被调用,子类的资源没有正确释放。

29.什么时候会发生段错误?

段错误通常发生在访问非法内存地址的时候:使用野指针,试图修改字符串常量的内容。

30.new/malloc的区别?

(1)new分配内存按照数据类型进行分配,malloc分配内存按照指定的大小分配。
(2)new返回的是指定对象的指针,而malloc返回的是void*,需要进行类型转化。
(3)new不仅会分配一段内存,而且会调用构造函数。
(4)new分配的内存要用delete销毁,(会调用对象的析构函数),malloc要用free销毁。
(5)new是一个操作符可以重载,malloc是一个库函数。

31.源文件从文本到可执行文件的过程

1.预处理阶段:将源代码中文件包含关系(头文件),预编译语句(宏定义)进行分析和替换,生成预编译文件 gcc –E hello.c –o hello.i
2.编译阶段:将预处理阶段生成的预编译文件转换成特定的汇编代码,生成汇编文件 gcc –S hello.i –o hello.s
3.汇编阶段:将编译阶段生成的汇编文件转化为机器码,生成可重定位目标文件 gcc –c hello.s –o hello.o
4.链接阶段:将多个目标文件及所需要的库连接成最终的可执行目标文件。 gcc hello.o –o hello

32.volatile是干啥用的,(必须将cpu的寄存器缓存机制回答的很透彻),使用实例有哪些?(重点)

1)访问寄存器比访问内存单元要快,编译器会优化减少内存的读取,可能会读脏数据。声明变量为volatile,编译器不再对访问该变量的代码优化,仍然从内存读取,使访问稳定。

总结:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不再编译优化,以免出错。

2)使用实例如下(区分C程序员和嵌入式系统程序员的最基本的问题。):

并行设备的硬件寄存器(如:状态寄存器)
一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
多线程应用中被几个任务共享的变量
3)一个参数既可以是const还可以是volatile吗?解释为什么。

可以。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
4)一个指针可以是volatile 吗?解释为什么。
可以。尽管这并不很常见。一个例子当中断服务子程序修该一个指向一个buffer的指针时。

下面的函数有什么错误:
int square(volatile int *ptr) {
return ptr * ptr;
}
下面是答案:
这段代码有点变态。这段代码的目的是用来返指针
ptr指向值的平方,但是,由于
ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr){
int a,b;
a = *ptr;
b = ptr;
return a * b;
}
由于
ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr){
int a;
a = *ptr;
return a * a;
}

33.分别设置和清除一个整数的第三位?

static int a;
void set_bit3(void){ 
    a |= BIT3;
} 
void clear_bit3(void){ 
    a &= ~BIT3;
} 

34.extern c 作用

告诉编译器该段代码以C语言进行编译。

35.关于静态内存分配和动态内存分配的区别及过程

  1. 静态内存分配是在编译时完成的,不占用CPU资源;动态分配内存运行时完成,分配与释放需要占用CPU资源;

2)静态内存分配是在栈上分配的,动态内存是堆上分配的;

3)动态内存分配需要指针或引用数据类型的支持,而静态内存分配不需要;

4)静态内存分配是按计划分配,在编译前确定内存块的大小,动态内存分配运行时按需分配。

5)静态分配内存是把内存的控制权交给了编译器,动态内存把内存的控制权交给了程序员;

6)静态分配内存的运行效率要比动态分配内存的效率要高,因为动态内存分配与释放需要额外的开销;动态内存管理水平严重依赖于程序员的水平,处理不当容易造成内存泄漏。

36.头文件中的 ifndef/define/endif 干什么用?

预处理,防止头文件被重复使用,包括pragma once都是这样的

37.库函数的实现:

memcpy函数的实现

void *memcpy(void *dest, const void *src, size_t count) {
 char *tmp = dest;
 const char *s = src;
 while (count--)
  *tmp++ = *s++;
  return dest;
}

Strcpy函数实现

char *strcpy(char *dst,const char *src) { 
      assert(dst != NULL && src != NULL); 
      char *ret = dst; 
      while((* dst++ = * src++) != '\0') ; 
      return ret; 
 }

strcat函数的实现

char *strcat(char *strDes, const char *strSrc){
	assert((strDes != NULL) && (strSrc != NULL));
	char *address = strDes;
	while (*strDes != ‘\0)
		++ strDes;
	while ((*strDes ++ = *strSrc ++) != ‘\0)
	return address;
}

strncat实现

char *strncat(char *strDes, const char *strSrc, int count){
	assert((strDes != NULL) && (strSrc != NULL));
	char *address = strDes;
	while (*strDes != ‘\0)
		++ strDes;
	while (count — && *strSrc != ‘\0)	
		*strDes ++ = *strSrc ++;
	*strDes = ‘\0;
	return address;
}

strcmp函数实现

int strcmp(const char *str1,const char *str2){

    /*不可用while(*str1++==*str2++)来比较,当不相等时仍会执行一次++,

    return返回的比较值实际上是下一个字符。应将++放到循环体中进行。*/
    while(*str1 == *str2){
        if(*str1 == '\0')
            return0;       

        ++str1;
        ++str2;
    }
    return *str1 - *str2;
}

strncmp实现

int strncmp(const char *s, const char *t, int count){
    assert((s != NULL) && (t != NULL));
    while (*s && *t && *s == *t && count –) {
        ++ s;
        ++ t;
    }
    return (*s – *t);
}

strlen函数实现

int strlen(const char *str){
    assert(str != NULL);
    int len = 0;
    while (*str ++ != ‘\0)
        ++ len;
    return len;
}

strpbrk函数实现

char * strpbrk(const char * cs,const char * ct){
    const char *sc1,*sc2;
    for( sc1 = cs; *sc1 != '\0'; ++sc1){
        for( sc2 = ct; *sc2 != '\0'; ++sc2){
            if (*sc1 == *sc2){
                return (char *) sc1;
            }
        }
    }
    return NULL;
}

strstr函数实现

char *strstr(const char *s1,const char *s2){
 int len2;
 if(!(len2=strlen(s2)))//此种情况下s2不能指向空,否则strlen无法测出长度,这条语句错误
  return(char*)s1;
 for(;*s1;++s1)
 {
     if(*s1==*s2 && strncmp(s1,s2,len2)==0)
     return(char*)s1;
 }
 return NULL;
}

string实现(注意:赋值构造,operator=是关键)

class String{
public:
	//普通构造函数
	String(const char *str = NULL);
	//拷贝构造函数
	String(const String &other);
	//赋值函数
	String & operator=(String &other) ;
	//析构函数
	~String(void);
private:
	char* m_str;
};

分别实现以上四个函数

//普通构造函数
String::String(const char* str){
    if(str==NULL) //如果str为NULL,存空字符串{
        m_str = new char[1]; //分配一个字节
        *m_str = ‘\0; //赋一个’\0′
}else{
       str = new char[strlen(str) + 1];//分配空间容纳str内容
        strcpy(m_str, str); //复制str到私有成员m_str中
    }
}

//析构函数
String::~String(){
    if(m_str!=NULL) //如果m_str不为NULL,释放堆内存{
        delete [] m_str;
        m_str = NULL;
	}
}

//拷贝构造函数
String::String(const String &other){
    m_str = new char[strlen(other.m_str)+1]; //分配空间容纳str内容
    strcpy(m_str, other.m_str); //复制other.m_str到私有成员m_str中 
} 
//赋值函数
String & String::operator=(String &other){
    if(this == &other) //若对象与other是同一个对象,直接返回本{
        return *this
}
    delete [] m_str; //否则,先释放当前对象堆内存
    m_str = new char[strlen(other.m_str)+1]; //分配空间容纳str内容
    strcpy(m_str, other.m_str); //复制other.m_str到私有成员m_str中
    return *this}

虚函数与纯虚函数区别

1)虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现

2)带纯虚函数的类叫虚基类也叫抽象类,这种基类不能直接生成对象,只能被继承,重写虚函数后才能使用,运行时动态动态绑定!

深拷贝与浅拷贝

浅拷贝:
char ori[]=“hello”;char *copy=ori;

深拷贝:
char ori[]=“hello”; char *copy=new char[]; copy=ori;

浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。

浅拷贝可能出现的问题:
1) 浅拷贝只是拷贝了指针,使得两个指针指向同一个地址,这样在对象块结束,调用函数析构的时,会造成同一份资源析构2次,即delete同一块内存2次,造成程序崩溃。
2) 浅拷贝使得两个指针都指向同一块内存,任何一方的变动都会影响到另一方。
3) 同一个空间,第二次释放失败,导致无法操作该空间,造成内存泄漏。

stl各容器的实现原理(必考)

  1. Vector顺序容器,是一个动态数组,支持随机插入、删除、查找等操作,在内存中是一块连续的空间。在原有空间不够情况下自动分配空间,增加为原来的两倍。vector随机存取效率高,但是在vector插入元素,需要移动的数目多,效率低下。

:vector动态增加大小时是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对vector空间重新配置,指向原vector的所有迭代器就都失效了。

  1. Map关联容器,以键值对的形式进行存储,方便进行查找。关键词起到索引的作用,值则表示与索引相关联的数据。红黑树的结构实现,插入删除等操作都在O(logn)时间内完成。

  2. Set是关联容器,set每个元素只包含一个关键字。set支持高效的关键字检查是否在set中。set也是以红黑树的结构实现,支持高效插入、删除等操作。

多重继承有什么问题? 怎样消除多重继承中的二义性?

1)增加程序的复杂度,使程序的编写和维护比较困难,容易出错;

2)继承类和基类的同名函数产生了二义性,同名函数不知道调用基类还是继承类,C++中使用虚函数解决这个问题

3)继承过程中可能会继承一些不必要的数据,对于多级继承,可能会产生数据很长

可以使用成员限定符和虚函数解决多重继承中函数的二义性问题。

C++中哪些不能是虚函数?

1)普通函数只能重载,不能被重写,因此编译器会在编译时绑定函数。
2)构造函数是知道全部信息才能创建对象,然而虚函数允许只知道部分信息。
3)内联函数在编译时被展开,虚函数在运行时才能动态绑定函数。
4)友元函数 因为不可以被继承。
5)静态成员函数 只有一个实体,不能被继承。父类和子类共有。

如何判断一段程序是由C 编译程序还是由C++编译程序编译的?

#ifdef __cplusplus
cout<<"C++";
#else
cout<<"c";
#endif

为什么要用static_cast转换而不用c语言中的转换?

Static_cast转换,它会检查类型看是否能转换,有类型安全检查。
比如,这个在C++中合法,但是确实错误的。
A* a= new A;
B* b = (B*)a;

操作符重载(+操作符),具体如何去定义?

除了类属关系运算符”.”、成员指针运算符”.*”、作用域运算符”::”、sizeof运算符和三目运算符”?:”以外,C++中的所有运算符都可以重载。
<返回类型说明符> operator <运算符符号>(<参数表>){}
重载为类的成员函数和重载为类的非成员函数。参数个数会不同,应为this指针。

explicit是干什么用的 ?

构造器 ,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。explicit是用来防止外部非正规的拷贝构造的,要想不存在传值的隐式转换问题。

必须使用初始化列表初始化数据成员的情况

1.是对象的情况;
2.const修饰的类成员;
3.引用成员数据;

类成员变量的初始化不是按照初始化表顺序被初始化,是按照在类中声明的顺序被初始化的。

析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?

C++标准指明析构函数不能、也不应该抛出异常。

1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。

2)通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。

写一个c程序辨别系统是64位 or 32位

void* number = 0; printf("%d\n",sizeof(&number));

输出8就是64位 输出4就是32位的 根据逻辑地址判断的

8.写一个c程序辨别系统是大端or小端字节序

union{ short value; char a[sizeof(short)];}test;

test.value= 0x0102;

if((test.a[0] == 1) && (test.a[1] == 2)) cout << “big”<<endl; else cout << “little” << endl;

i++ 是否原子操作?并解释为什么?

答案肯定不是原子操作,i++主要看三个步骤

首先把数据从内存放到寄存器上,在寄存器上进行自增处理,放回到寄存器上,每个步骤都可能会被中断分离开!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值