C++ Introduction to Object-Oriented Programming (1)

Scope keywords:

private: (private) self-friend self-cultivation friendly class

protected: (friendly) subclass--self--friend self-cultivation friendly class

pubilc: (public) wherever you can see, class variables will do

Class: The functions implemented in the class are inline by default, and the default type is private

class student

{

student(){} parameterless constructor

student(int a,char *objname):arg(a),name( objname )//initialization list, can improve efficiency const object, reference object must use initialization list

{

//Function body

}

~student(){}//Destructor


void function1(void)

{

//inline function

}

void function2(void);

pubilc:

int arg;

char *name;

const int grade; // const   initialization must be done in the initialization list of the destructor, not in the function

}

voidstudent::function2(void)

{

//General functions

}

Constructor: It is used to initialize the object when it is created, that is, to assign initial values ​​to the object member variables. It is always used in the statement of creating the object together with the new operator. A particular class can have multiple constructors, which can be distinguished according to the number of its parameters or the different types of parameters, that is, the overload of the constructor.

(1) Why can't the constructor have a return value

·如果它们有返回值,要么编译器必须知道如何处理返回值,要么就只能由客户程序员自己来显式的调用构造函数与析构函数,这样一来,安全性就被人破坏了.另外,析构函数不带任何参数,因为析构不需任何选项.
如果允许构造函数有返回值,在某此情况下,会引起歧义。返回不知道给谁

(2)为什么构造函数不能为虚函数

·虚函数调用的机制,是知道接口而不知道其准确对象类型的函数,但是创建一个对象,必须知道对象的准确类型;当一个构造函数被调用时,它做的首要事情之一就是初始化它的VPTR来指向VTABLE。

(3)当构造函数是单参数时加上explicit修饰,单参数的构造函数,可能存在隐式转换的问题,因为单参数构造函数,和拷贝构造函数形式类似,调用时很可能会发生隐式转换,应加上explicit关键字

explicitstudent(int a):arg(a){}

(4) 对于初始化列表而言,对成员变量的初始化,是严格按照声明次序,而不是在初始化列表中的顺序进行初始化

(5)C++构造函数初始化按下列顺序被调用:
·首先,任何虚拟基类的构造函数按照它们被继承的顺序构造;
·其次,任何非虚拟基类的构造函数按照它们被继承的顺序构造;
·最后,任何成员对象的构造函数按照它们声明的顺序调用;


#include <iostream>
using namespace std;
class OBJ1{
public:
OBJ1(){ cout<<"OBJ1\n"; }
};
class OBJ2{
public:
OBJ2(){ cout<<"OBJ2\n";}
}
class Base1{
public:
Base1(){ cout<<"Base1\n";}
}
class Base2{
public:
Base2(){ cout <<"Base2\n"; }
};
class Base3{
public:
Base3(){ cout <<"Base3\n"; }
};
class Base4{
public:
Base4(){ cout <<"Base4\n"; }
};
class Derived :public Base1, virtual public Base2,public Base3, virtual public Base4//继承顺序{
public:
Derived() :Base4(), Base3(), Base2(),Base1(), obj2(), obj1(){//初始化列表
cout <<"Derived ok.\n";
}
protected:
OBJ1 obj1;//声明顺序
OBJ2 obj2;
};

int main()
{
Derived aa;//初始化
cout <<"This is ok.\n";
return 0;
}
结果:
Base2 //虚拟基类按照被继承顺序初始化
Base4 //虚拟基类按照被继承的顺序 
Base1 //非虚拟基类按照被继承的顺序初始化
Base3 //非虚拟基类按照被继承的顺序 
OBJ1 //成员函数按照声明的顺序初始化
OBJ2 //成员函数按照声明的顺序 
Derived ok. 
This is ok.

析构函数

赋值函数:

拷贝构造函数:

拷贝构造函数和赋值构造函数的异同
由于并非所有的对象都会使用拷贝构造函数和赋值函数,程序员可能对这两个函数有些轻视。请先记住以下的警告,在阅读正文时就会多心:如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。以类String 的两个对象a,b 为例,假设a.m_data 的内容为“hello”,b.m_data 的内容为“world”。现将a 赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。这将造成三个错误:一是b.m_data 原有的内存没被释放,造成内存泄露;二是b.m_data 和a.m_data 指向同一块内存,a 或b 任何一方变动都会影响另一方;三是在对象被析构时,m_data 被释放了两次。拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。



类String 的拷贝构造函数与赋值函数
  // 拷贝构造函数
  String::String(const String &other)//这里必须是引用,否则会无限循环
  {

//拷贝构造函数可理解为类的拷贝但是重新开辟了空间,用已有的对象初始化新类对象

  // 允许操作other 的私有成员m_data
  int length = strlen(other.m_data);
  m_data = new char[length+1];
  strcpy(m_data, other.m_data);
  }
  // 赋值函数
  String & String::operator =(const String &other) 
  {

//重载修饰符operator     格式 (const String &other)  返回值为类的引用
  // (1) 检查自赋值
  if(this == &other)
  return *this;
  // (2) 释放原有的内存资源
  delete [] m_data;
  // (3)分配新的内存资源,并复制内容
  int length = strlen(other.m_data);
  m_data = new char[length+1];
  strcpy(m_data, other.m_data);
  // (4)返回本对象的引用
  return *this;
  }

下面那些为拷贝构造函数那些为赋值函数:

String a(“hello”);
  String b(“world”);
  String c = a; // 调用了拷贝构造函数,最好写成 c(a);
  c = b; // 调用了赋值函数
  本例中第三个语句的风格较差,宜改写成String c(a) 以区别于第四个语句。

 类String 拷贝构造函数与普通构造函数的区别是:在函数入口处无需与NULL 进行比较,这是因为“引用”不可能是NULL,而“指针”可以为NULL。类String 的赋值函数比构造函数复杂得多,分四步实现:
  (1)第一步,检查自赋值。你可能会认为多此一举,难道有人会愚蠢到写出 a = a 这样的自赋值语句!的确不会。但是间接的自赋值仍有可能出现,例如
  // 内容自赋值
  b = a;
  …
  c = b;
  …
  a = c;
  // 地址自赋值
  b = &a;
  …
  a = *b;
  也许有人会说:“即使出现自赋值,我也可以不理睬,大不了化点时间让对象复制自己而已,反正不会出错!”他真的说错了。看看第二步的delete,自杀后还能复制自己吗?所以,如果发现自赋值,应该马上终止函数。注意不要将检查自赋值的if 语句
  if(this == &other)
  错写成为
  if( *this == other)
  (2)第二步,用delete 释放原有的内存资源。如果现在不释放,以后就没机会了,将造成内存泄露。
  (3)第三步,分配新的内存资源,并复制字符串。注意函数strlen 返回的是有效字符串长度,不包含结束符‘\0’。函数strcpy 则连‘\0’一起复制。
  (4)第四步,返回本对象的引用,目的是为了实现象 a = b = c 这样的链式表达。注意不要将 return *this 错写成 return this 。那么能否写成return other 呢?效果不是一样吗?不可以!因为我们不知道参数other 的生命期。有可能other 是个临时对象,在赋值结束后它马上消失,那么return other 返回的将是垃圾。
  偷懒的办法处理拷贝构造函数与赋值函数
  如果我们实在不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,怎么办?
  偷懒的办法是:只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。
  例如:
  class A
  { …
  private:
  A(const A &a); // 私有的拷贝构造函数
  A & operator =(const A &a); // 私有的赋值函数
  };
  如果有人试图编写如下程序:
  A b(a); // 调用了私有的拷贝构造函数
  b = a; // 调用了私有的赋值函数
  编译器将指出错误,因为外界不可以操作A 的私有函数。


1、  拷贝构造函数首先是一个构造函数,它调用的时候产生一个对象,是通过参数传进来的那个对象来初始化,产生的对象。  
   operator=();是把一个对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检查一下两个对象是不是同一个对象,如果是的话就不做任何操作。

2、还要注意的是拷贝构造函数是构造函数,不返回值   
   而赋值函数需要返回一个对象自身的引用,以便赋值之后的操作  

Guess you like

Origin blog.csdn.net/hgz_gs/article/details/51832836