【复习】C++面向对象基础1


C++面向对象基础

根据侯捷老师的C++面向基础课程总结而来,内容经过博主适当扩充。(仅作为复习用,最好有C基础或者C++部分基础)

1.class分类

  • class without pointer members

  • class with pointer members

将c++的类分作不带指针和带有指针两种,他们的主要区别是

  • 带有指针,即数据是通过指针间接访问的,所以在拷贝构造时需要处理这个数据。(决定了你拷贝的多个对象的某个数据成员,是多个指针变量指向同一个内存区域,还是每个对象的指针成员指向的不同存储区域。)
  • 带有指针的类,通常需要自己处理指针相关的内存区域,如初始化和析构函数,以免内存泄漏。
  • 而不带指针的类不需要手动管理内存,这和Java中的类会比较像。

这种划分有一个比较简单的想法是,C++的每个class对象的大小应该是已知固定的,这样在创建对象分配内存时编译器知道该分多少内存。这时考虑一下,字符串,每个字符串长度不一样,那么字符串类怎么确定长度。所以采用指针,指针变量的存储空间是固定的,只需要让不同对象中的指针成员指向不同具体存储数据的空间,那么就可以解决变长的数据问题了。


2.C++历史简述

  • B语言
  • C语言
  • C++(早期是c with class)

C++版本

  • c++ 98 (1.0)
  • c++ 03
  • c++ 11(2.0)
  • c++14
  • c++17 (建议扩充学习c++17新特性)

c++包含:

  • 语言部分,描述C++语法
  • C++标准库,常用功能的实现与封装。

3.C与C++的区别

这里只讨论数据和内存模型,不涉及具体的语法区别。

  • C:数据和函数独立,不关联。
  • C++:通过class将数据和函数组织(绑定)在一起,它考虑到实际上所有的函数都是在处理某些特定的数据,所以将他们绑定在一起,而不需要与其他无关的数据耦合。

class的好处只要具体编写过面向过程编程和面向对象编程就能体会到。不同的面向对象语言的思想是一样的,有句经典的话叫

万物皆对象。

不管是抽象的还是具体的概念都可以是对象。(在很久以前刚接触面向对象编程的时候很不解,面向对象怎么就方便了,怎么就减少复杂性了,每一个面向过程需要编写的函数在面向对象里面还不是一样要写成员函数的实现吗?事实上,这种结构思想上的改变在小程序还体现不出来,但当工程量大起来的时候,就是一盘散沙和一堆砖块的区别。)

补充:C++如何绑定成员变量和成员函数:https://www.cnblogs.com/alone-striver/p/7875741.html

每个对象所占用的存储空间只是该对象的数据部分(虚函数指针和虚基类指针也属于数据部分)所占用的存储空间,而不包括函数代码所占用的存储空间。

C++程序的内存格局通常分为四个区:全局数据区(data area),代码区(code area),栈区(stack area),堆区(heap area)(即自由存储区)

  • 全局数据区存放全局变量,静态数据和常量;
  • 所有类成员函数和非成员函数代码存放在代码区
  • 为运行函数而分配的局部变量、函数参数、返回数据、返回地址等存放在栈区;(所以在无退出条件的递归函数调用时会发生栈溢出错误.)
  • 余下的空间都被称为堆区。

类成员函数是被放在代码区,而类的静态成员变量在类定义时就已经在全局数据区分配了内存,因而它是属于类的。对于非静态成员变量,我们是在类的实例化过程中(构造对象)才在栈区或者堆区为其分配内存,是为每个对象生成一个拷贝,所以它是属于对象的。

总结:

  • 类的静态成员变量:全局数据区,属于类,每个类一份。
  • 类的非静态成员变量:栈区,属于类的每一个对象实体,每个对象独立一份,互不干扰。
  • 类的成员函数:代码区。属于类。每个该类的对象都会调用到这同一份成员函数代码。(关于虚函数和静态成员函数后续关于三大OOP操作再详细讨论,TODO

4.“善变的”struct

  • C中的struct VS C++中的struct

    • C语言中的struct是用户自定义数据类型,它是没有权限设置的,它只能是一些变量的集合体,虽然可以封装数据却不可以隐藏数据,而且成员不可以是函数。
    • C++语言中的struct是抽象数据类型(ADT),它支持成员函数的定义,同时他增加了访问权限,它的成员函数默认访问权限是public。
    • C语言中的struct是没有继承关系的,而C++的struct却有丰富的继承关系。
  • C++中的struct VS C++中的class

    • 如果没有多态和虚拟继承,在C++中,struct和class的存取效率完全相同,存取class的数据成员与非虚函数效率和struct完全相同,不管该数据成员是定义在基类还是派生类。

      class的数据成员在内存中的布局不一定是数据成员的声明顺序,C++只保证处于同一个access section的数据成员按照声明顺序排列。

    • 对于成员的默认访问权限,class是private,struct是public。

    • 默认继承权限不同,class继承默认是private继承,而struct默认是public继承。需要注意的是,class可继承class,也可继承struct。struct可继承struct,也可继承class。默认的继承方式取决于子类是struct还是class

    • 定义模版参数,使用typename,也可以使用class。但是不能使用struct。

在编写C++代码时,强烈建议使用 class 来定义类,而使用 struct 来定义结构体,这样做语义更加明确。


5.防卫式头文件声明

#ifndef __HEAD_FILE_NAME__
#define __HEAD_FILE_NAME__

// 头文件内容
// ...

#endif

原因:由于C++不允许重复定义,所以在include头文件的时候,可能出现不同文件交叉include,带来重复定义问题。所以这个模式保证了每个头文件只会被include一次。

具体的含义:

  • #ifndef:if no define,如果没有定义xxx。
  • #define:那么定义xxx
  • #endif:end if,结束该if块。

这是一个比较正统的头文件编写模板!


6.一个class的例子

// complex.h
#ifndef __COMPLEX__
#define __COMPLEX__

#include <cmath>

// 前置声明:forward declarations
class ostream;
class complex;
complex&
__doapl (complex* ths, const complex& r);

// 类的声明:class declarations
class complex
{
...
};

// 类的定义:class definition
complex::function ...

    
#endif

说明:

  • 前置声明:前置声明作用是告诉编译器下文中会用到一些标识符,他们是有具体含义的,不要因为你不知道而给我报错了。其实这里的前置声明作用还不明显,更明显的作用可查看一个交叉引用的struct定义的例子(就是说定义struct B的时候有成员类型是struct A,定义strcut A的时候,里面有成员类型是struct B,这时该如何编码):

    https://blog.csdn.net/f290131665/article/details/17678851

  • 类的声明:向编译器描述你的类有哪些数据成员变量,有哪些函数(也可在声明的时候就实现了该函数——默认是inline模式)。

  • 类的定义:成员函数的实现。


7.C++模板和Java泛型的区别

Java中的泛型是“类型擦除”,源码编译成字节码时,参数类型消除。

// 编译前:代码
	Vector<String> vector = new Vector<String>();
	vector.add(new String("hello"));
	String str = vector.get(0);

// 编译后:实际
Vector vector = new Vector();
vector.add(new String("hello"));
String str = (String)vector.get(0);

看到,Java的泛型世界上是强制类型转换。虚拟机常量池中只有一个方法,以及方法字节码中一些多出来的cast指令,泛型参数在class文件里是不存在。Java中虚拟机中没有泛型,只有基本类型和类类型,泛型会被擦除,一般会修改为Object

除了Java,所有实现在jvm上的语言,如果能与java兼容的(互相调用之类的),那么他必定也是这种泛型实现方式,例如kotlin,groovy

C++的模板则是创建多个具有不同参数的方法,所以最后的可执行文件中,也必定包含了多个方法。这一现象被称为“模板代码膨胀”。

// C++
template <typename T> T max(T a,T b){
    return a>b?a:b;
}
// 可以编译,在实际调用max的时候,会判断传入的参数T是否支持>操作符,如果不支持才会报错

// Java
 static <T extends Comparable<T>>T max(T a,T b){
        return a.compareTo(b)>0?a:b;
    }
// 编译出错,因为不能确定a是否有compareTo方法,这需要在编译阶段就确定好类型。一个解决方案就是采用Java中的泛型的类型上限约束,如下:
 static <T extends Comparable<T>>T max(T a,T b){
        return a.compareTo(b)>0?a:b;
    }

具体就可以想象成, Java的泛型只有一个方法,所以它的类型必须符合严格的要求,而C++有多个互不干涉的方法,每个方法有自己的类型要求。Java的泛型检查在编译期,运行时没有泛型。C++的泛型在编译器运行时都存在。

C++: MyClass不会与MyClass共享静态变量。然而,两个MyClass实例则会共享静态变量。

Java:MyClass类的静态变量会由所有MyClass实例共享,无论类型参数相与否.

  • C++模板可以使用int等基本数据类型。Java则不行,必须转而使用Integer
  • Java中,可以将模板的类型参数限定为某种特定类型。例如,你可能会使用泛型实现A,并规定参数必须扩展自B。也就是上文提到的泛型类型限定。
  • Java中,类型参数(即MyClass中的Foo)不能用于静态方法和变量,因为他们会被MyClass和MyClass共享。但在C++中,这些类是不同的,类型参数可以用于静态方法和静态变量。
  • 在Java中,不管类型参数是什么,MyClass的所有实例都是同一类型。类型参数会在运行时被抹去。而C++中,参数类型不同,实例类型也不同

8.inline与宏的区别及其优缺点

#define TABLE_MULTI(x) (x*x)

因为函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。即,函数调用会在栈中进行“转场”,有内存(空间+时间)开销。

而宏是编译时的代码展开,“实际”代码量会增长,同时,宏会存在一些歧义。如上述宏定义:TABLE_MULTI(10),结果返回100,是正确的。如果我们用TABLE_MULTI(10+10)去调用的话,我们期望的结果是400,而宏的调用结果是(10+10*10+10),结果是120.

// 避免方法:参数用括号括起来
#define TABLE_MULTI(x) ((x)*(x))

但此时若用TABLE_MULTI(a++)调用,希望得到 ( a + 1 ) ∗ ( a + 1 ) (a+1)*(a+1) (a+1)(a+1),但结果 ( a + + ) ∗ ( a + + ) (a++)*(a++) (a++)(a++)

内联函数

  • 任何在class的声明部分内定义实现的函数都自动被认为是内联函数。

  • 内联函数必须是和函数体申明在一起,才有效。像这样的申明inline Tablefunction(int I)是没有效果的,编译器只是把函数作为普通的函数申明,我们必须定义函数体。

    inline tablefunction(int I) {return I*I};
    
  • 内联函数只是一种对编译器的建议,建议把该函数编译成“内联”的方式,实际在编译过程中,编译器根据该函数的具体复杂度决定最终它能否被编译成一个真正的内联函数。

区别

  • 内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。

  • 内联函数与带参数宏定义的另一个区别是,内联函数的参数类型和返回值类型在声明中都有明确的指定;而带参数宏定义的参数没有类型的概念,只有在宏展开以后,才由编译器检查语法,这就存在很多的安全隐患。

  • inline是指嵌入代码,就是在调用函数的地方不是跳转,而是把代码直接写到那里去。可以加快程序运行的速度,因为不需要中断调用.

  • 预处理器可以删除注释、包含其他文件以及执行宏替代。所以宏是预处理处理,inline是编译器处理。


9.构造函数初始化

初始化列表与函数体初始化的区别

class complex
{
public:
    // 1.通过initialization list初始化成员变量
    complex (double r = 0, double i = 0): re (r), im (i) 
    { }
    
    // 2.通过assignments,赋值成员变量(方法1,2只能选1个,这里为了说明,两个都写出来了。)
    complex (double r = 0, double i = 0)
	{ re = r; im = i; }
};

  • initialization list初始化成员变量,仅在构造函数中可以这样写。
  • 上面的代码中可见,构造函数不能有返回值,函数名和class name相同。参数推荐使用传引用参数。参数可以有默认值,但有默认值的参数必须放在参数列表的后面。
  • 推荐使用方法1的构造函数,因为它在创建成员变量的时候直接初始化到目标值。而方法二分两步,先随机初始化(与编译器有关,可能不是随机,可能为0初始化),然后再通过赋值语句赋值,所以效率低了一点。
// 初始化列表应该按声明的顺序,不要轻易改变其顺序。
class CMyClass {   
CMyClass(int x, int y);   
int m_x;   
int m_y;   
};   
  
CMyClass::CMyClass(int i) : m_y(i), m_x(m_y)   
{   
  
}

你 可能以为上面的代码将会首先做m_y=I,然后做m_x=m_y,最后它们有相同的值。但是编译器先初始化m_x,然后是m_y,,因为它们是按这样的顺序声明的。结果是m_x将有一个不可预测的值。


10.函数重载条件

  • 重载判断:参数类型,参数个数,参数顺序,函数名相同。(返回值类型不作为重载判断

  • 含有默认参数的重载函数,在调用的时候会发生调用错误,编译器不知道到底该调用哪个函数,此时,可用函数指针解决该问题,或者用namespace解决。

  • C没有函数重载,但是cpp有函数重载,故cpp兼容c时,需要用到extern关键字。(C++的重载函数,编译器在编译的时候实际上为每个函数重新命名了的)

void test(int a,int b=1)
{
	cout<< a << b;
}

void test(int a)
{
	cout << a;
}

int main(int argc, char const *argv[])
{
	// 调用test的时候,若test(2);则不知道如何匹配
	// 此时用namespace或者函数指针解决
	void (*fun1)(int a,int b) = test;
	// 用指针解决的时候,默认参数不再起作用,要显式传入参数值
	fun1(2,1);  
	return 0;
}

11.namespace

  • namespace作用:解决类重名、函数重名、变量重名等重名问题。用来给资源(变量、函数)限定一个名称。

  • namespace内容的访问类型:namespace中所有的内容都是public的,且不能用private修饰。

  • C没有namespace关键字,cpp才有命名空间概念。

  • 命名空间的定义:namespace space_name { body…}

  • 命名空间的使用:

    • using namespace space_name ;
    • space_name ::body_name
  • 匿名namespace:匿名namespace,表示可直接引用该space内的内容,而不用space_name ::的方式调用该域内容。其作用和static类似,将资源限定在本文件中使用,外部文件是无法访问的(叫不上名字)

    // 匿名空间
    namespce {
        char c;
        int i;
        double d;
    }
    
    // 编译器的等效结果
    namespace __UNIQUE_NAME_ {
        char c;
        int i;
        double d;
    }
    using namespace __UNIQUE_NAME_;
    // 在匿名命名空间中声明的名称也将被编译器转换,与编译器为这个匿名命名空间生成的唯一内部名称(即这里的__UNIQUE_NAME_)绑定在一起。还有一点很重要,就是这些名称具有internal链接属性,这和声明为static的全局名称的链接属性是相同的,即名称的作用域被限制在当前文件中,无法通过在另外的文件中使用extern声明来进行链接。
    // C++ 新的标准中提倡使用匿名命名空间,而不推荐使用static,因为static用在不同的地方,涵义不同,容易造成混淆.另外,static不能修饰class。
    
  • 命名空间别名

    namespace name1_long_name{}
    namespace name2 = name1_long_name;//变量赋值的方式为命名空间取别名
    
  • 扩充命名空间:

    • 再次重定义命名空间(空间名相同)则会扩展原命名空间的内容,而不像变量那样因重定义而编译出错。
    • 但是,在扩展的时候,不能再次重定义变量,不会覆盖而是发生重定义编译错误。
    • 不建议在命名空间中直接定义函数,而是采用函数指针变量的方式,在命名空间外部定义函数。
  • using作用域

    • using作用只从该语句到本源文件结束。
    • 若using语句在block内部,则该using只作用在该代码块中
    • using语句只能在命名空间定义之后的地方使用
    • 一个源文件可用同时使用多个using语句,但是若同时using的多个命名空间有名称相同的内容,则引用该内容时需要显式的采用域操作符::方式。
  • C中全局变量与CPP命名空间

    • C中,当函数中局部变量与全局变量重名时,在该函数中无法引用该全局变量
    • CPP中,可用::操作符解决上述情景,可在函数中引用重名的全局变量,::name表示name是全局的变量,而不是该代码块中的局部变量
    • 综上所述,cpp中的::域操作符,若之前有标识符,表示命名空间,无标识符,表示全局变量。
    #include<iostream>
    
    using namespace std;
    
    // 自定义命名空间
    namespace name1 {
    	int a = 10;
    	void print() {
    		cout << "print in space name1 !" << endl;
    	}
    }
    
    // 匿名空间
    namespace {
    	int a = 20;
    	void print() {
    		cout << "print in space none_name !" << endl;
    	}
    }
    
    // 全局命名空间
    int a = 30;
    void print() {
    	cout << "print in globle space !" << endl;
    }
    
    int main() {
    	// 编译错误,a不明确,编译器不知道是全局空间的a还是匿名空间的a
    	// cout << a << endl;
    	// 同样错误,有多个重载的print, 全局print和<unamed>::print()
    	// print();
    	
    	// 30,调用的是全局的变量
    	cout << ::a << endl;
    	// 同理,调用全局的print函数
    	::print();
    
    	// 10,调用的是name1的变量
    	cout << name1::a << endl;
    	// 同理,调用name1的print函数
    	name1::print();
    
    	// 所以无法访问到匿名空间中的资源!?
    	return 0;
    }
    

12.const member functions 常成员函数

const与宏常量的区别

  1. const常量有数据类型,而宏常量没有数据类型

编译器可以对前者进行类型安全检查,而对后者只能进行字符替换,没有安全检查,并且在字符替换时可能会产生意料不到的错误。

  1. 编译器对二者的调试

有些集成化的调试工具可以对const常量进行调试, 在 c++程序中只使用const常量而不使用宏常量,即const常量完全取代宏常量。

class complex
{
public:
    complex (double r = 0, double i = 0)
    : re (r), im (i) 
    { }
    // const 写在参数括号之后,无数据修改的成员函数都强烈建议写成const的。(不写不报错,但是使用中会出现问题)
    double real () const { return re; }
    double imag () const { return im; }
private:
    double re, im;
};

13.参数传递(value VS. reference)

  • 值传递:参数在传递过程中,会拷贝一份,若是复杂对象,会带来很多额外的开销。所以推荐只在基本数据类型时采用值传递。

  • 引用传递:reference参数接收的实参可以是显式地reference也可以不是reference而是value,传递者无需知道接受者怎么接收的,接收者无需知道传递者怎么传的。引用与指针类似(底层就是指针实现)。它传递的是对象的地址,所以在参数传递过程中开销很小。

    inline complex&
    __doapl(complex* ths, const complex& r)
    {
    ...
    return *ths;
    }
    inline complex&
    complex::operator += (const complex& r)
    {
    return __doapl(this,r);
    }
    
  • 常引用传递:但由于是地址传递,所以引用传递往往存在一个问题,有可能在函数体中修改了函数调用处(外部)的实参。这个时候需要根据实际需求来增加const修饰,若要保证这个传进来的引用的数据不被函数体修改,需要增加const限制。(大多数情况下都是如此)


14.参数返回(value VS. reference)

函数的返回值,同样需要考虑直接返回值还是返回结果的引用

  • return value:基本数据类型。这种返回会发生数据拷贝。所以自定义类型带指针一定要实现拷贝构造函数,否则进行函数调用值传递时会出现问题。(假设在函数中声明一个临时对象A,A中p指针数据new了一个内容。然后函数执行完之后,结果A值传递给调用处,B接收A,若只发生浅拷贝,B中的p指针也指向A中的p指针指向的内容,此时,函数调用结束,自动回收临时对象A,A的析构中自然会delete p,那么B就会引用到一个被回收的内存,会错误。)
  • return reference:显然返回reference是比较好的,它避免的大规模的拷贝。但是,函数调用返回结果时,往往不能直接返回结果的引用!这是因为在函数调用内部的变量都是在栈中创建的局部临时变量(Local variables),它在函数调用后,会被销毁(空间回收),所以引用无法通过地址访问到正确的数据。这种情况下不能返回引用!若函数返回值不是局部变量,比如是一个由参数传入的外部引用,那么自然就可以返回该引用(若需要的话)。
  • 要想在函数调用中返回reference,可以用new的方式将对象创建在堆中,这样函数调用结束后,它就不会被自然回收了(new创建对象直接使用堆空间,而局部不用new定义类对象则使用栈空间)。如下列代码中的get_str2

string & get_str(string & str) {
	return str;
}

string & get_str() {
	string local = "abc";
	cout << "局部变量local的地址:" << &local << endl;
	return local; // 这里传递的时候,可以直接传对象值,不用显式写引用。
}

string & get_str2() {
    // 但这种new的方式容易造成内存泄漏
    // 普通方式创建,使用完后不需要手动释放,该类析构函数会自动执行。而new申请的对象,则只有调用到delete时再会执行析构函数,如果程序退出而没有执行delete则会造成内存泄漏。
	string* local = new string("abc");
	cout << "局部变量local的地址:" << local << endl;
	return *local; 
}

int main() {
	string a = string("abc");
	// abc, a address : 0000000C2FAFF7B8
	// abc, get_str(a) address : 0000000C2FAFF7B8
	cout << a << ", a address:" << &a << endl;
	cout << get_str(a) << ", get_str(a) address:" << &(get_str(a)) << endl;

	// 局部变量local的地址:000001D57008F760
	// get_str2() address : 000001D57008F760
	cout << "get_str2() address:" << &(get_str2()) << endl;

	// 局部变量local的地址:0000000C2FAFF638
	// 中止!在调用string c = get_str();会发生一个拷贝构造,访问到一个被销毁的内存空间,出现内存错误
	string c = get_str();

	return 0;
}

15.友元 friend

  • 关键字friend
  • 注意:友元关系只是单向关系,且不具备传递性。即我当你是朋友,你不一定当我是朋友;你是我朋友,你的朋友不一定是我朋友
  • 用法:友元类、友元函数
  • 友元函数的实现可以在类外定义,但必须在类内部声明。友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类
  • 一定程度上,友元打破了C++的封装性,给class开辟了一个额外的访问路径。

16.访问控制

封装(访问权限针对的是class,而不是单个对象,private修饰的资源只能被本类class的object访问,而不是说当前object访问当前object的previate修饰的资源)。直接表现:相同class的各个objects互为友元

class complex
{
public:
    complex (double r = 0, double i = 0)
    : re (r), im (i) 
    { }
    int func(const complex& param)
    { return param.re + param.im; }
private:
    double re, im;
};

// 使用代码
{
    complex c1(2,1);
    complex c2;
    c2.func(c1);
    // c2的func中直接访问c1的private成员。这是可以的,本身private的作用单元就是class,而不是object
}

17.操作符重载

  • 操作符重载不改变优先级、结合性、操作数,能创建新操作符。

  • C++中操作符实际等价与函数调用,其重载操作符函数常规语法如下:
    类型 类名 :: operator 操作符(参数列表)

  • 对于操作符重载需要确定三点:几元操作符、操作数类型、返回值类型。对应不同的操作符重载函写法。对于全局函数,其参数个数、类型、顺序就是操作数的个数、类型、顺序。对于类成员操作符重载函数,可以看作是当前类对象为该操作符的默认第一个操作数(通过this指针引用这个当前对象)。所以操作符重载又可以分为两种写法:全局函数或者成员函数(同一功能,只能选一种实现)。如下

    class Foo
       {
       public:
       int operator + (int i)
       {
          cout<<"Foo + override " << endl;
          return 0;
       }
     };
    
    // 编译器将这个函数和Foo的成员函数视为相同的重载函数,编译会出错
    int operator + (Foo a,int b)
    {
        cout << "global + override. " << endl;
        return 0;
    }
    
    // 实际上,这两个同样的“+”重载是站在不同角度编写的同样意义的函数
    int main(int argc, char *argv[])
    {
        Foo foo;
        int i = foo + 1;
        return 0;
    }
    
  • 但有些重载必须写成全局函数,如 输出操作符“<<”。

  • 操作符重载往往要考虑使用习惯,考虑连续操作问题。

  • 特别注意:++、-- 、new 、delete、new [] 、delete [] 重载参数问题。new\delete必须成对重载。new xxx[] 与 new xxx不同,且new xxx[] 通常与delete [] 成对重载。new和delete一定会调用构造、析构函数。malloc、free不会调用析构和构造。即重载new、和delete并不会完全摒弃其原始功能(即使重载了,还是会调用构造析构),仅是干涉(或者说添加)一些自定义的逻辑。一般不建议重载new和delete。

  • 操作符重载基本条件:一定有一个操作数是自定义的C++类,即所有操作数都是基本数据类型的操作符重载是非法的。


18.小结(1)

写一个类该考虑哪些问题?

  • 有哪些数据?数据应该private保护起来。
  • 有哪些方法?通常方法都是public,公开给外部调用,简单的方法通常会inline实现。
  • 方法会改变private的数据吗?如果不改变,那么该方法应该被修饰为const。
  • 参数建议传引用。
  • 引用参数会在函数体内inplace修改嘛?如果不修改传进来的参数,那么参数应该是const 的引用传递。
  • 函数返回应该是返回value还是返回reference?如果返回的内容是在函数体内创建的local变量,那么不应该return reference,因为在函数调用栈中创建的局部变量在函数调用结束后它就会被回收,那么外部通过这个引用访问这个空间就会出问题。
  • 操作符重载需要写成成员函数还是非成员函数?只能选择其一,且有些特殊的操作符只能用非成员函数重载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值