C++中类相关知识

C++中 类

1.this指针

  class应该理解为一种类型,象int,char一样,是用户自定义的类型。用这个类型可以来声明一个变量,比如int x, myclass my等等。这样就像变量x具有int类型一样,变量my具有myclass类型。理解了这个,就好解释this了,my里的this 就是指向my的指针。如果还有一个变量myclass mz,mz的this就是指向mz的指针。 这样就很容易理解this 的类型应该是myclass *,而对其的解引用*this就应该是一个myclass类型的变量。

  通常在class定义时要用到类型变量自身时,因为这时候还不知道变量名(为了通用也不可能固定实际的变量名),就用this这样的指针来使用变量自身。

  1. this指针的用处:
      一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。
      this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。例如,调用date.SetMonth(9) <===> SetMonth(&date, 9),this帮助完成了这一转换 .
      在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无需通过成员访问运算符来做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看成this的隐式使用。
      this的目的总是指向这个对象,所以this是一个常量指针,我们不允许改变this中保存的地址
  2. this指针的使用:
      一种情况就是,在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;
      另外一种情况是当参数与成员变量名相同时,如this->n = n (不能写成n = n)。
  3. this指针程序示例:
      this指针是存在与类的成员函数中,指向被调用函数所在的类实例的地址。
      根据以下程序来说明this指针:
#include<iostream.h>
class Point
{ 
  int x, y;
public:
  Point(int a, int b) { x=a; y=b;}
  Void MovePoint( int a, int b){ x+=a; y+=b;}
  Void print(){ cout<<"x="<<x<<"y="<<y<<endl;}
};
void main( )
{
   Point point1( 10,10);
   point1.MovePoint(2,2);
   point1.print( );
}

  当对象point1调用MovePoint(2,2)函数时,即将point1对象的地址传递给了this指针。
  MovePoint函数的原型应该是 void MovePoint( Point *this, int a, int b);第一个参数是指向该类对象的一个指针,我们在定义成员函数时没看见是因为这个参数在类中是隐含的。这样point1的地址传递给了this,所以在MovePoint函数中便显式的写成:
  void MovePoint(int a, int b) { this->x +=a; this-> y+= b;}
  即可以知道,point1调用该函数后,也就是point1的数据成员被调用并更新了值。
  即该函数过程可写成 point1.x+= a; point1. y + = b;
转载自:https://blog.youkuaiyun.com/chenyt01/article/details/51316022


2.类中构造函数的初始化列表

2.1一般类中构造函数的初始化列表

  构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
  初始化列表的成员初始化顺序:C++初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序。
  构造函数两种初始化情况举例:

class CExample {
public:
    int a;
    float b;
    //构造函数初始化列表
    CExample(): a(0),b(8.8) {}
    //构造函数内部赋值
    CExample() {
        a=0;
        b=8.8;
    }
};

上面的例子中两个构造函数的结果相同。例子中,

  • CExample(): a(0),b(8.8) {}:使用初始化列表的构造函数显式的初始化类的成员;
  • CExample() { a=0; b=8.8; }:未使用初始化列表的构造函数是对类的成员赋值,并没有进行显式的初始化;
    初始化和赋值对内置类型的成员无大的区别。但对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表

必须用带有初始化列表的构造函数的情况:

  1. 成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
  2. const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。

上述情况1示例:

class Pet {
private: 
	string type;
public: 
	Pet(string type) { this->type = type; }
	string getPetType() { return this->type; }
};
class InPet {
private:
	Pet ipet;
	int count;
public:
	InPet(Pet ipet,int count) : ipet(ipet){
		//this->ipet = ipet;//此种情况只能使用初始化列表
		this->count = count;
	}
};

总结:
初始化数据成员与对数据成员赋值的含义是什么?有什么区别?
首先把数据成员按类型分类并分情况说明:

  1. 内置数据类型,复合类型(指针,引用)
    在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的
  2. 用户定义类型(类类型)
    结果上相同,但是性能上存在很大的差别。因为类类型的数据成员对象在进入函数体前已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,调用构造函数,在进入函数体之后,进行的是对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)

转载自:https://www.cnblogs.com/BlueTzar/articles/1223169.html

2.2派生类构造函数的初始化列表

  派生类不能继承基类的构造函数,必须自己定义构造函数进行新增数据成员初始化工作,如果想同时初始化基类数据成员,必须调用基类构造函数。

class stu1 {
public:
    stu1(int i, string strn, double in) {
        ID = i;
        name = strn;
        income = in;
    }
private:
    int ID;
    string name;
    double income;
};

class stu2 :public stu1
{
public:
    stu2(int i, const string str, double in, string p) :stu1(i, str, in)  {
        phoneNo = p;
    }
private:
    string phoneNo;
};

int main() {
    stu2 st(1, "Tom", 1000.02, "12345");
}

  构造一个类的对象之前,必须先构造其中的嵌套类对象,若没给嵌套类传参数,则调用嵌套类的默认构造函数,否则调用嵌套类的带参数的构造函数。

转载自:https://www.cnblogs.com/hi3254014978/p/9810170.html


3 具有继承关系的父类与子类之间的类型转换问题

  具有继承关系的父类与子类之间的类型转换:

  • 从子类到父类大类型转换:向上转型,不需特殊操作
  • 从父类到子类大类型转换:向下转型,需要 dynamic_cast<>操作

析:子类转父类
  子类是从父类继承而来,子类中包含父类中所有成员。在转换成父类的过程中,意味着对子类进行了一个切割,只是将子类中的父类部分赋值给了父类对象。
  但在子类转换成父类的时候,也需要注意,
只能是指针或者引用,不能是对象

析:父类转子类
  对于父类转换成子类,意味着将子类中将有一部分是未知的成员。这就需要dynamic_cast<>

示例:

#include <iostream>
using namespace std;

class CPoint
{
public:
	virtual void f() { cout << "CPoint::f" << endl; }
};

class CPoint3D : public CPoint
{
public:
	virtual void f() { cout << "CPoint3D::f" << endl; }
};

int main()
{
	CPoint point, *pPoint;
	CPoint3D point3D, *pPoint3D;

	//子类向父类类型转换,直接强制转换就可以了,但实际仍然指向子类对象
	pPoint = (CPoint*)&point3D;
	pPoint->f(); //调用的仍然是子类的函数,这便是虚函数的魅力所在

	//父类向子类类型转换,要用dynamic_cast
	pPoint3D = dynamic_cast<CPoint3D*>(pPoint);
	pPoint->f(); //pPoint实际指向一个CPoint3D对象,故可以正确转换

	pPoint3D = dynamic_cast<CPoint3D*>(&point);
	cout << (int*)pPoint3D << endl; //&point指向的不是一个CPoint3D对象,返回值为NULL
	return 0;
}

结果:

CPoint3D::f
CPoint3D::f
00000000
  • dynamic_cast<type-id> ( expression )
    定义:将一个基类对象指针(或引用)cast到继承类指针,dynamic_cast<>会根据基类指针是否真正指向继承类指针来做相应处理。
    作用:该运算符把expression转换成type-id类型的对象。type-id 必须是类的指针、类的引用或者void*
    析:如果 type-id 是类指针类型,那么expression也必须是一个指针,如果 type-id 是一个引用,那么 expression 也必须是一个引用。

dynamic_cast运算符可以在执行期决定真正的类型。

  • 如果 downcast 是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。
  • 如果 downcast 不安全,这个运算符会传回空指针(NULL)(也就是说,基类指针或者引用没有指向一个派生类对象)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值