C++学习 之 类中的特殊函数和this指针

1.构造函数

  构造函数是一种特殊的函数,它在对象被创建时被调用,与类同名无返回类型,可以被重载。构造函数的可以在类内实现也可以在类外实现。

  构造函数的声明类似于下面的代码:

class Human
{
 public:
    Human();//构造函数声明
};

  构造函数在类声明中实现类似于下面的代码:

class Human
{
public:
    Human ()
    {
        //构造函数的实现部分
    }
};

  构造函数在类的声明外实现类似于下面的代码:

class Human
{
public:
    Human ();
};
Human::Human()
{
     //构造函数的实现部分
};

  使用构造函数便于我们对类内的属性进行初始化,确保已知属性中不包含随机值。构造函数也是可以重载的,这说明在调用构造函数时有时是需要提供参数的,可以在不提供参数的情况下调用的构造函数

称作默认构造函数。在调用构造函数初始化对象的属性时,又需要提供不同的参数,这时重载构造函数就可以为我们提供帮助。

  构造函数的重载类似于下面的代码:

class Human
{
private:
    string Name;
    int Age;
public:
    Human (int HumanAge)
    {
       Age=HuanAge;
    }
    Human (string HumanName,int HumanAge)
    {
        Name=HumanName;
        Age=HumanAge;
    }
};

  其实构造函数不仅可以重载,其参数还可以带有默认值。

  例如下面这样:

class Human
{
private:
    string Name;
    int Age;
public:
    Human (int HumanAge)
    {
       Age=HuanAge;
    }
    Human (string HumanName,int HumanAge=22)//与上一段代码的区别在于给出了HumanAge的默认值
    {
        Name=HumanName;
        Age=HumanAge;
    }
   
   /*Human (int HumanAge,string HumanName="Tom")//去掉多行注释符号后,这种重载会报错,原因在于当提供一个int型的参数调用构造函数时,系统并不知道该调用Human(int)Human(int,string HumanName="Tom")
    {
        Name=HumanName;
        Age=HumanAge;
    }*/
};

2.析构函数

  与析构函数一样,析构函数也看上去和类同名,只是在函数名前多了波浪号"~"。每当对象不再在作用域内或通过delete被删除,进而被销毁时都将调用析构函数。这使得析构函数是重置变量以及释放动态分配的内存和其他资源的理想场所。析构函数不能够重载每个类只能有一个析构函数,如果忘记实现析构函数,系统会自动生成一个伪析构函数,由于伪析构函数为空其不能释放动态分配的内存空间。

  析构函数的类内实现类似于下面的代码:

class Human
{
 public:
    ~Human()
   {
       //析构函数实现部分
   }

};

  析构函数的类外实现类似于下面的代码:

class Human
{
 public:
    ~Human();
};
Human::Human()
{
  //析构函数实现部分
};

  对象所在的函数已调用完毕时,系统自动执行析构函数;用new开辟了一片内存空间,delete会自动调用析构函数后释放内存;对象A是对象B的成员,B的析构函数被调用时,对象A的析构函数也会被调用。

3.复制构造函数

  3.1浅复制及其存在的问题:

    当类中包含指针成员,定义的类的对象又作为实参传递给某个函数的形参(即被复制)时,指针成员将被复制。复制后的指针成员和原对象中的指针成员指向同一个内存空间,这被称为浅复制,会威   胁程序的稳定性。

  例如下面的程序就存在问题:

#include<iostream>
#include<cstring>
using namespace std;
void strcpy_s(char* pre, const char* next)
{
    if (next != NULL)
    {
        while ((*pre++ = *next++) != ‘\0‘);
    }
}
class MyString
{
private:
    char* Buffer;
public:
    MyString(const char *InitialInput)
    {
        if (InitialInput != NULL)
        {
            Buffer = new char[strlen(InitialInput) + 1];//Buffer指向新分配的空间
            strcpy_s(Buffer, InitialInput);
        }
        else
            Buffer = NULL;
    }
    ~MyString()
    {
        if(Buffer!=NULL)
        {
            cout << "析构,释放内存" << endl;
            if (Buffer != NULL)
                delete[] Buffer;
        }
    }
    int GetLength()
    {
        return strlen(Buffer);
    }
    const char* GetString()
    {
        return Buffer;
    }
};
void UseMyString(MyString Input)//调用该函数时实参对象会被复制给形参Input
{
    cout << "Buffer 字数为:" << Input.GetLength();
    cout << "Buffer 内容为:" << Input.GetString();
    return;
}
int main()
{
    MyString SayHello("Hello");
    UseMyString(SayHello);
    return 0;
}

  在调用UseMyString函数时,实参SayHello会被浅复制给形参Input。在传递参数的过程中,SayHello里有个成员指针Buffer,SayHello.Buffer会把地址传给Input.Buffer(这就像之前的函数的参数的传递方式一样);这样会导致它们指向同一块内存空间。当UseMyString函数调用完成时会调用析构函数释放Inpu的内存空间,然后返回主函数,当主函数执行完成后会调用析构函数释放SayHello的内存空间,这时便会出现问题,因为Input.Buffer和SayHello.Buffer指向了同一块内存空间,此时却连续执行了两次析构函数释放了同一块内存空间(程序会崩溃或无法返回正常值)。

  浅复制存在的最大问题便是会可能出现复制出的对象与原对象共用某块内存的现象,这样很可能会在释放所占内存时出问题。我们便引入复制构造函数进行深度复制解决类似问题。

  3.2 使用复制构造函数:

    复制构造函数是一种特殊的重载构造函数,我们在使用类时必须提供它。每当对象被复制其中包括把对象当作参数按值传递给函数时,编译器都将调用复制构造函数。复制构造函数接受一个以引用方   式传入的当前类的对象作为参数。这个参数是源对象的别名,我们在构造函数内使用它来复制源对象,确保对所有缓冲区进行复制。

    下面给出上面浅复制修改为深复制的代码:

#include<iostream>
#include<cstring>
using namespace std;
void strcpy_s(char* pre, const char* next)
{
    if (next != NULL)
    {
        while ((*pre++ = *next++) != ‘\0‘);
    }
}
class MyString
{
private:
    char* Buffer;
public:
    MyString(const char* InitialInput)
    {
        if (InitialInput != NULL)
        {
            Buffer = new char[strlen(InitialInput) + 1];
            strcpy_s(Buffer, InitialInput);
        }
        else
            Buffer = NULL;
    }
    MyString(const MyString& CopySource)//复制构造函数
    {
        cout <<"从源对象复制:"<< endl;
        if (CopySource.Buffer != NULL)
        {
            Buffer = new char[strlen(CopySource.Buffer) + 1];
            strcpy_s(Buffer, CopySource.Buffer);
        }
        else
            Buffer = NULL;
    }
    ~MyString()
    {
        if(Buffer!=NULL)
        {
            cout << "析构,释放内存" << endl;
            if (Buffer != NULL)
                delete[] Buffer;
        }
    }
    int GetLength()
    {
        return strlen(Buffer);
    }
    const char* GetString()
    {
        return Buffer;
    }
};
void UseMyString(MyString Input)
{
    cout << "Buffer 字数为:" << Input.GetLength()<<endl;
    cout << "Buffer 内容为:" << Input.GetString()<<endl;
    return;
}
int main()
{
    MyString SayHello("Hello");
    UseMyString(SayHello);
    return 0;
}

4.this指针

  在C++中,一个重要的概念是保留的关键字this,在类中,关键字this包含当前对象的地址,换句话说,其值为&object。当您在类成员方法中调用其他成员方法时,编译器将隐式地传递this指针——函数 调用中不可见的参数:

#include<iostream>
#include<cstring>
using namespace std;
class Human
{
private:
    int Age;
    char Name;
public:
    void SetAge(int HumansAge)
    {
        Age = HumansAge;//修改当前对象的Age值
   cout<<Age;
    }
};
int main()
{
    Human FirstMan;
    FirstMan.SetAge(21);
    return 0;
}  

  在上面对象FirstMan调用类中静态函数时就存在着this指针的隐式传递,即调用FirstMan(21)相当于相当于FirstMan(this,21);对对象成员Age的访问相当于this->Age=HumanAge。在整个过程中this指向当前对象,存储当前对象的地址。

 :关于this指针的一个经典回答:

  当你进入一个房子后,

  你可以看见桌子、椅子、地板等,

  但是房子你是看不到全貌了。

  对于一个类的实例来说,

  你可以看到它的成员函数、成员变量,

  但是实例本身呢?

  this是一个指针,它时时刻刻指向你这个实例本身。

  

  

  

原文地址:https://www.cnblogs.com/dulm/p/11249506.html

时间: 2024-11-10 08:12:28

C++学习 之 类中的特殊函数和this指针的相关文章

Spring MVC普通类或工具类中调用service报空空指针的解决办法(调用service报java.lang.NullPointerException)

当我们在非Controller类中应用service的方法是会报空指针,如图: 这是因为Spring MVC普通类或工具类中调用service报空null的解决办法(调用service报java.lang.NullPointerException) 按上述步骤解决完自己的工具类后,你会发现项目运行后仍然报空指针此时你需要在applicationContext.xml 配置文件中添加一行配置文件 如图: 对自己工具类所在的包进行注解扫描,使Spring能够识别自己上面所配置的注解 原文地址:htt

mfc 在VC的两个对话框类中传递参数的三种方法

弄了好久,今天终于把在VC中的对话框类之间传递参数的问题解决了,很开心,记录如下: 1. 我所建立的工程是一个基于MFC对话框的应用程序,一共有三个对话框,第一个对话框为主对话框,所对应的类为CTMDDDlg类.在主对话框上我放置了一个标签页(Tab Control)控件,其实现的功能是当单击标签提示A时进入页面A,即对话框A(所对应的类为CDialogChild1),单击B时进入对话框B(CDialogChild2). 整个工程的框架已经设计好了,在对话框A和对话框B上放置了许多控件,现在我想

学习IOS开问题篇--类中的成员变量如果不实例化是什么情况

@interface Person : NSObject @property (noatonmic,copy) NSString * name; @end 一个person类,name是person得成员变量 如果在一个类中写入person为成员变量 self.person.name = @"zhangsan"; 如果前面不将person实例化,实际上是在对一个空指针进行操作 [nil setname:@"zhangsan"]; 因为oc中对空指针发消息不会报错,所

Cocos2d-x 3.1.1 学习日志2--error:只有静态常量整型数据成员才可以在类中初始化

今天遇到比较低端的一个问题,就是成员的初始化问题,编译器也无法验证,不同的编译器有些能过有些不能过,我也不知道为什么,总是我们以vs为准吧,以为我们用的环境就是它,话不多说,解决方案如下: 在类中    static const double PI=3.1416; error:只有静态常量整型数据成员才可以在类中初始化 常整型静态数据成员可以在类中直接初始化,而常实型静态数据成员不可以 class circle { int a; // 普通变量,不能在类中初始化 static int b; //

Scala学习(五)---Scala中的类

Scala中的类 摘要: 在本篇中,你将会学习如何用Scala实现类.如果你了解Java或C++中的类,你不会觉得这有多难,并且你会很享受Scala更加精简的表示法带来的便利.本篇的要点包括: 1. 类中的字段自动带有getter方法和setter方法 2. 你可以用定制的getter/setter方法替换掉字段的定义,而不必修改使用类的客户端,这就是所谓的"统一访问原则" 3. 用@BeanProperty注解来生成JavaBeans的getXxx/setXxx()方法 4. 每个类

Guava库学习:Guava中Obejects实用工具类的学习

链接地址:http://www.xx566.com/detail/128.html Java中的Object类是所有Java类的超类(也就是祖先),所有对象都实现Object类中的方法,在日常的工作中,我们经常需要重写其中的几个 方法, 如:equals.toString.hashCode等方法,而在工作中,我们实现这些方法有时候也比较痛苦,如equals方法判断非空. toString调试信息不完整等等,在Guava中,其提供了Objects类帮助我们简化了这些常用方法的实现,接下来,我们一起

转:python学习——类中为什么要定义__init__()方法

学习Python的类,一直不太理解为什么一定要定义init()方法,现在简要谈一下自己的理解吧. 1.不用init()方法定义类定义一个矩形的类,目的是求周长和面积. 1 class Rectangle(): 2 def getPeri(self,a,b): 3 return (a + b)*2 4 def getArea(self,a,b): 5 return a*b 6 7 rect = Rectangle() 8 print(rect.getPeri(3,4)) 9 print(rect.

在学习c++过程中,总结类的三个用户以及使用权限,感觉非常实用

首先我们需要知道类的三个用户分别是:类的实现者,类的普通用户和类的继承者(派生类),接下来分别讲解这几种用户的区别. 1 .类的实现者:顾明思议,就是类的设计者,拥有最大的权限,可以访问类中任何权限的成员,主要负责编写类的成员和友元的代码.可以访问类中的公有部分(public),保护部分(protect)和(private)私有部分. 2.类的普通用户:就是使用类的对象,这部分用户只能访问类的接口(也就是公用部分poublic). 3.类的继承者:就是派生类.派生类能访问基类中的公有部分和受保护

设计模式的学习(二)-UML中的类图及类图之间的关系

统一建模语言(Unified Modeling Language,UML)是用来设计软件蓝图的可视化建模语言,为面向对象的建模语言的国际标准.他的特点是简单,统一,图形化,能表达软件设计中的动态与静态信息. 统一建模语言能为软件开发的所有阶段提供模型化和可视化支持,而且融入了软件工程领域的新思想.新方法和新技术,使软件设计人员沟通更简明,进一步缩短的设计时间,减少开发成本,它的应用领域很宽,不仅适合与一般系统的开发,而且适合于并行与分布式系统的建模. UML从目标系统的不同角度出发,定义了用例图