常见c++问题



成员函数的重载、覆盖与隐藏

成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。

    覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。

“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)不同的范围(分别位于派生类与基类);
(2)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(3)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

在覆盖调用中,函数的行为取决于指针所指向的对象。在隐藏调用中,函数的行为取决于指针的类型。


默认构造函数是指:无参构造函数或每个参数均有默认值的构造函数。

默认的构造函数分为有用的和无用的,所谓无用的默认构造函数就是一个空函数、什么操作也不做,而有用的默认构造函数是可以初始化成员的函数。
对构造函数的需求也是分为两类:一类是编辑器需求,一类是程序的需求。
程序的需求:若程序需求构造函数时,就是要程序员自定义构造函数来显示的初始化类的数据成员。
编辑器的需求:编辑器的需求也分为两类:一类是无用的空的构造函数(trivial),一类是编辑器自己合成的有用的构造函数(non-trivival)。

在用户没有自定义构造函数的情况下:

一、由于编辑器的需求,编辑器会调用空的无用的默认构造函数。如:类中没有显式定义任何构造函数。

二、但在某些情况下,编辑器就一定会合有用的默认构造函数。

如果你没有为你的类提供任何构造函数,那么编译器将自动为你生成一个默认的无参构造函数。一旦你为你的类定义了构造函数,哪怕只是一个,那么编译器将不再生成默认的构造函数。

有以下三种情况,编译器一定会产生默认构造函数:

(1)该类、该类的基类或该类中定义的类类型成员对象中,有虚函数存在。

发生这种情况时,由于必须要完成对象的虚表初始化工作,所以编译器在没有任何构造函数的时候,会产生一个默认构造函数来完成这部分工作;然而,如果已经有任何构造函数,编译器则把初始化虚表这部分工作“合成”到你已定义的构造函数之中。

(2)该类、该类的基类中所定义的类类型成员对象中,带有构造函数。

发生这种情况时,由于需要显式地调用这些类类型成员的构造函数,编译器在没有任何构造函数的时候,也会产生一个默认构造函数来完成这个过程;同样,如果你已经定义一个构造函数但没有对这些类类型成员显式调用构造函数,编译器则把这部分工作“合成"到你定义的构造函数中。

(3)该类拥有虚基类。

发生这种情况,需要维护“独此一份"的虚基类继承而来的对象,所以也需要通过构造函数完成。方式同(1)(2)。

除上述3种情况外,“可认为在没有任何构造函数时候,编译器产生一个默认构造函数”是不对的,因为这样的默认构造函数是“无用”的,编译器也就不会再用心良苦去做没用的工作。


首先来看,我们“知道”的构造函数,C++构造函数究竟做了哪些事情?

1、创建一个类的对象时,编译器为对象分配内存空间,然后调用该类的构造函数;

2、构造函数的目的,是完成对象非静态成员的初始化工作(静态成员如何初始化?记住以下要点:在类外进行、默认值为0、在程序开始时、在主函数之前、单线程方式、主线程完成),记住:C++类非静态成员是没有默认值的(可对比Java)。

3、如果构造函数有初始化列表,则先按照成员声明顺序(非初始化列表中的顺序)执行初始化列表中的内容,然后再进入构造函数体。这里又有疑问了,如果类本身没有非虚拟的基类,应显式地调用直接基类的某个构造函数,否则,将会自动其直接基类的默认构造函数(如果此时直接基类没有默认构造函数,得到编译错误);如果类本身有虚拟基类,也应显式地调用虚拟基类的某个构造函数,否则,将会自动调用虚拟基类的默认构造函数;如果成员有其它类的对象,则应显式地调用成员所属类的相应构造函数,否则对于没有在初始化列表中出现的类成员,也会自动调用其默认的构造函数

注意上述调用顺序,编程时应按照“先祖再客最后自己”的原则进行,即,首先完成自身包含的“祖先对象”的初始化,之后,完成自身包含的成员是其它类型(客人)的初始化,最后才是自身非类类型成员的初始化工作。


Any time you have a constructor, whether it’s a struct with all members public or a class with private data members, all the initialization must go through the constructor, even if you’re using aggregate initialization.




构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段.
所有类类型(class type)的成员都会在初始化阶段初始化,即使该class type成员没有出现在构造函数的初始化列表中,也会在初始化阶段初始化。
计算阶段一般用于执行构造函数体内的赋值操作。
初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。使用初始化列表主要是基于性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?因为所有类类型(class type)的成员都会在初始化阶段初始化,即使该class type成员没有出现在构造函数的初始化列表中,也会在初始化阶段初始化。所以对于使用构造函数体赋值class type类型成员来说,使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。
除了性能问题之外,有些时场合初始化列表是不可或缺的,以下几种情况时必须使用初始化列表:
常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。

类成员变量的初始化不是按照初始化表的顺序被初始化的,而是按照在类中声明的顺序被初始化的。
为什么会这样呢?我们知道,对一个对象的所有成员来说,它们的析构函数被调用的顺序总是和它们在构造函数里被创建的顺序相反。那么,如果允许上面的情况(即,成员按它们在初始化列表上出现的顺序被初始化)发生,编译器就要为每一个对象跟踪其成员初始化的顺序,以保证它们的析构函数以正确的顺序被调用。这会带来昂贵的开销。所以,为了避免这一开销,同一种类型的所有对象在创建(构造)和摧毁(析构)过程中对成员的处理顺序都是相同的,而不管成员在初始化列表中的顺序如何。
注意:上述内容不适用于static变量,static变量应该在类的构造函数前被初始化。



拷贝构造函数

语句"CExample B=A;"  用 A 初始化 B。 其完成方式是内存拷贝,复制所有成员的值。 完成后,A.pBuffer = B.pBuffer,  即它们将指向同样的地方,指针虽然复制了,但所指向的空间并没有复制,而是由两个对象共用了。这样不符合要求,对象之间不独立了,并为空间的删除带来隐患。 所以需要采用必要的手段(拷贝构造函数)来避免此类情况。 

拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的唯一的一个参数是本类型的一个引用变量,该参数是const类型,不可变的。


当用一个已初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动调用。也就是说,当类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
一个对象以值传递的方式传入函数体
一个对象以值传递的方式从函数返回
一个对象需要通过另外一个对象进行初始化。

如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝,后面将进行说明。

自定义拷贝构造函数是一种良好的编程风格,它可以阻止编译器形成默认的拷贝构造函数,提高源码效率。


浅拷贝和深拷贝

  在某些状况下,类内成员变量需要动态开辟堆内存,如果实行位拷贝,也就是把对象里的值完全复制给另一个对象,如A=B。这时,如果B中有一个成员变量指针已经申请了内存,那A中的那个成员变量也指向同一块内存。这就出现了问题:当B把内存释放了(如:析构),这时A内的指针就是野指针了,出现运行错误。

  深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。

CExample(const CExample&); //拷贝构造函数




赋值构造函数

如果类需要析构函数,则它也需要复制操作符 和 复制构造函数,这个规则被称为 C++的“三法则”。如果需要手动定义了其中了一个,那么另外的两个也需要定义,通常在存在指针或者前期相关操作的情况下,都需要手动的定义。

 C = A;

也用到了"="号,但与上面的例子中语句“ CExample B=A;  ” 不同“ CExample B=A;  语句中的 "=" 在对象声明语句中,表示初始化。更多时候,这种初始化也可用括号表示。 例如 CExample B(A); 

    而本例子中,"=" 表示赋值操作。将对象 A 的内容复制到对象C;,这其中涉及到对象C 原有内容的丢弃,新内容的复制。 但"="的缺省操作只是将成员变量的值相应复制。旧的值被自然丢弃。 由于对象内包含指针,将造成不良后果:指针的值被丢弃了,但指针指向的内容并未释放。指针的值被复制了,但指针所指内容并未复制。 因此,包含动态分配成员的类除提供拷贝构造函数外,还应该考虑重载"="赋值操作符号。 

CExample& operator = (const CExample&); //赋值符重载


//赋值操作符重载,记住形参和返回值一定要是引用类型,否则传参和返回时会自动调用拷贝构造函数
CExample & CExample::operator = (const CExample& RightSides)
{

    if(this==&RightSides) return *this;//避免自我赋值
     nSize=RightSides.nSize; //复制常规成员
    char *temp=new char [nSize]; //复制指针指向的内容 
     memcpy(temp,RightSides.pBuffer,nSize*sizeof (char ));

    delete []pBuffer; //删除原指针指向内容   (将删除操作放在后面,避免X=X特殊情况下,内容的丢失)
     pBuffer=temp;    //建立新指向
    return *this 
}
拷贝构造函数使用赋值运算符重载的代码。 

拷贝构造函数的参数一定是引用,如果不是引用,传参时按值传递,会出现无限递归

CExample::CExample(const CExample& RightSides)

{
     pBuffer=NULL;
     *this =RightSides      //调用重载后的"="
}



基类虚析构函数作用
用基类指针指向子类,delete指针时,可以正确的先调用子类析构,避免子类资源泄露。


多重继承中为避免水晶结构导致子类有多个基类,子类需virtual虚继承基类。

该情况下如果基类有构造函数,每个子类在构造时都必须显示构造基类,否则编译失败(例外情况:基类提供了默认构造函数时例外)。

且基类仅由最后继承的子类来构造。

me++1228


类内存布局:先基类继承顺序,再成员声明顺序


资源应该被封装在一个对象里,遵循这个规则,你通常就能避免在存在异常环境里发生 资源泄漏,在析构时释放资源,如智能指针。

C++仅仅能删除被完全构造的对象(fully contructed objects)调用析构函数, 只有一个对象的构 造函数完全运行完毕,这个对象才被完全地构造。

如果构造函数异常,因析构函数无法调用会导致资源泄露。

将类的资源型成员变量用智能指针封装,可以优雅的避免资源因构造失败而泄露。

me++


c++ const成员函数.
一些成员函数改变对象,一些成员函数不改变对象.
常量对象不能调用非常量成员函数,因为它可能企图修改常量的数据成员.但构造函数和析构函数从不定义为常量成员.当然有特殊情况,就是用mutable关键字修饰过的成员变量可以在声明为const的函数中被改变。


对于常量型成员变量和引用型成员变量的初始化,必须通过构造函数初始化列表的方式进行。在构造函数体内给常量型成员变量和引用型成员变量赋值的方式是行不通的。
静态常量整型数据成员才可以在类中初始化.


C语言中static的变量:
1).static局部变量
        a、静态局部变量在函数内定义,生存期为整个程序运行期间,但作用域与自动变量相同,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它。
        b、对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。
2).static全局变量
        全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。但是他们的作用域,非静态全局变量的作用域是整个源程序(多个源文件可以共同使用); 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件(.h或.cpp)内有效, 在同一源程序的其它源文件中不能使用它(注意,这一点和C++是不同的)。


关于C语言static变量的理解:
A、若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
B、若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
C、静态变量和全局变量放在程序的全局数据区,而不是在堆栈中分配,所以不可能导致堆栈溢出;
D、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题(线程不安全);


 C++中静态数据成员的特点有::
          1、静态数据成员必须初始化。仅仅在初始化时不受访问权限的约束;和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则。注意::仅仅是初始化时不遵守public/protected/private的规则。
          2、静态数据成员最好不要在.h文件中进行定义(初始化),而是放在.cpp文件中定义(初始化);不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行
          3、静态数据成员被类的所有对象所共享,包括类的派生类的所有对象;——即派生类和基类共享一个静态成员。
          4、静态数据成员的类型可是所属类自己,即在一个类中可以声明该类自己的类型的静态成员对象,但是,不可以定义普通的成员对象,普通数据成员的只能声明为所属类类型的 指针或引用.
          5、在const成员函数中,可以修改static成员变量的值。普通成员变量的值,是不能修改的。
    6、static成员函数只能访问static成员,不能访问非static成员,并且static成员函数不能定义为virtual、const、volatile 函数。



对于常量型成员变量和引用型成员变量的初始化,必须通过构造函数初始化列表的方式进行。在构造函数体内给常量型成员变量和引用型成员变量赋值的方式是行不通的。
静态常量整型数据成员才可以在类中初始化.


C语言中static的变量:
1).static局部变量
        a、静态局部变量在函数内定义,生存期为整个程序运行期间,但作用域与自动变量相同,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它。
        b、对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。
2).static全局变量
        全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。但是他们的作用域,非静态全局变量的作用域是整个源程序(多个源文件可以共同使用); 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件(.h或.cpp)内有效, 在同一源程序的其它源文件中不能使用它(注意,这一点和C++是不同的)。


关于C语言static变量的理解:
A、若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
B、若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
C、静态变量和全局变量放在程序的全局数据区,而不是在堆栈中分配,所以不可能导致堆栈溢出;
D、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题(线程不安全);


 C++中静态数据成员的特点有::
          1、静态数据成员必须初始化。仅仅在初始化时不受访问权限的约束;和其他数据成员一样,静态数据成员也遵守public/protected/private访问规则。注意::仅仅是初始化时不遵守public/protected/private的规则。
          2、静态数据成员最好不要在.h文件中进行定义(初始化),而是放在.cpp文件中定义(初始化);不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行
          3、静态数据成员被类的所有对象所共享,包括类的派生类的所有对象;——即派生类和基类共享一个静态成员。
          4、静态数据成员的类型可是所属类自己,即在一个类中可以声明该类自己的类型的静态成员对象,但是,不可以定义普通的成员对象,普通数据成员的只能声明为所属类类型的 指针或引用.
          5、在const成员函数中,可以修改static成员变量的值。普通成员变量的值,是不能修改的。
    6、static成员函数只能访问static成员,不能访问非static成员,并且static成员函数不能定义为virtual、const、volatile 函数。



#define 常量时,预处理会导致从定义处起为全局可见,const定义常量则可以避免。

类的const成员,只能在初始化列表中初始化,否则编译错误。

不过static const的常量在其定义时初始化,且对所有实例共享。

enum值在编译期确定,且有作用域,同时不占用内存,可以达到const目的。


返回值优化:

Rational a = 10;
Rational b(1, 2);
Rational c = a * b; // 在这里调用 operator*

编译器就会被允许消除在 operator*内的临时变量和 operator*返回的临时变量。它们 能在为目标 c 分配的内存里构造 return 表达式定义的对象。如果你的编译器这样去做,调用 operator*的临时对象的开销就是零:没有建立临时对象。你的代价就是调用一个构造函 数――建立 c 时调用的构造函数。而且你不能比这做得更好了,因为 c 是命名对象,命名对 象不能被消除(参见条款 M22)。不过你还可以通过把函数声明为 inline 来消除 operator* 的调用开销(不过首先参见 Effective C++ 条款 33):
// the most efficient way to write a function returning an object 

inline const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}


inline函数

c++强制类型转换:dynamic_cast、const_cast 、static_cast、

1)static_cast<T*>(a)
编译器在编译期处理
将地址a转换成类型T,T和a必须是指针、引用、算术类型或枚举类型。
表达式static_cast<T*>(a), a的值转换为模板中指定的类型T。在运行时转换过程中,不进行类型检查来确保转换的安全性。
static_cast它能在内置的数据类型间互相转换,对于类只能在有联系的指针类型间进行转换。可以在继承体系中把指针转换来、转换去,但是不能转换成继承体系外的一种类型

2)dynamic_cast<T*>(a)
在运行期,会检查这个转换是否可能。
完成类层次结构中的提升。T必须是一个指针、引用或无类型的指针。a必须是决定一个指针或引用的表达式。
dynamic_cast 仅能应用于指针或者引用,不支持内置数据类型
表达式dynamic_cast<T*>(a) 将a值转换为类型为T的对象指针。如果类型T不是a的某个基类型,该操作将返回一个空指针。
它不仅仅像static_cast那样,检查转换前后的两个指针是否属于同一个继承树,它还要检查被指针引用的对象的实际类型,确定转换是否可行。
如果可以,它返回一个新指针,甚至计算出为处理多继承的需要的必要的偏移量。如果这两个指针间不能转换,转换就会失败,此时返回空指针(NULL)。
很明显,为了让dynamic_cast能正常工作,必须让编译器支持运行期类型信息(RTTI)。

3)const_cast<T*>(a)
编译器在编译期处理
去掉类型中的常量,除了const或不稳定的变址数,T和a必须是相同的类型。
表达式const_cast<T*>(a)被用于从一个类中去除以下这些属性:const, volatile, 和 __unaligned。

4)reinterpret_cast<T*>(a)
编译器在编译期处理
任何指针都可以转换成其它类型的指针,T必须是一个指针、引用、算术类型、指向函数的指针或指向一个类成员的指针。
表达式reinterpret_cast<T*>(a)能够用于诸如char* 到 int*,或者One_class* 到 Unrelated_class*等类似这样的转换,因此可能是不安全的。


java c++主要区别

1.指针
JAVA语言让编程者无法找到指针来直接访问内存,并且增添了自动的内存管理功能,从而有效地防止了c/c++语言中指针操作失误,如野指针所造成的系统崩溃。但也不是说JAVA没有指针,虚拟机内部还是使用了指针,只是外人不得使用而已。这有利于Java程序的安全。
2.多重继承
c++支持多重继承,这是c++的一个特征,它允许多父类派生一个类。尽管多重继承功能很强,但使用复杂,而且会引起许多麻烦,编译程序实现它也很不容易。Java不支持多重继承,但允许一个类继承多个接口(extends+implement),实现了c++多重继承的功能,又避免了c++中的多重继承实现方式带来的诸多不便。
4.自动内存回收
Java程序中所有的对象都是用new操作符建立在内存堆栈上,这个操作符类似于c++的new操作符。下面的语句由一个建立了一个类Read的对象,然后调用该对象的work方法:
6.预处理功能
Java不支持预处理功能。c/c十十在编译过程中都有一个预编泽阶段,即众所周知的预处理器。预处理器为开发人员提供了方便,但增加丁编译的复杂性。JAVA虚拟机没有预处理器,但它提供的引入语句(import)与c十十预处理器的功能类似。
7.c++模板




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值