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=)//与上一段代码的区别在于给出了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) + ];//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 ;
}

  在调用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) + ];
strcpy_s(Buffer, InitialInput);
}
else
Buffer = NULL;
}
MyString(const MyString& CopySource)//复制构造函数
{
cout <<"从源对象复制:"<< endl;
if (CopySource.Buffer != NULL)
{
Buffer = new char[strlen(CopySource.Buffer) + ];
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 ;
}

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();
return ;
}

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

 :关于this指针的一个经典回答:
  当你进入一个房子后,
  你可以看见桌子、椅子、地板等,
  但是房子你是看不到全貌了。
  对于一个类的实例来说,
  你可以看到它的成员函数、成员变量,
  但是实例本身呢?
  this是一个指针,它时时刻刻指向你这个实例本身。

  

  

  

C++学习 之 类中的特殊函数和this指针(笔记)的更多相关文章

  1. PHP面向对象学习五 类中接口的应用

    类中接口的应用 接口:一种成员属性全部为抽象的特殊抽象类,在程序中同为规范的作用   抽象类:1.类中至少有一个抽象方法.2.方法前需要加abstract 接口: 1.类中全部为抽象方法,抽象方法前不 ...

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

    当我们在非Controller类中应用service的方法是会报空指针,如图: 这是因为Spring MVC普通类或工具类中调用service报空null的解决办法(调用service报java.la ...

  3. python学习-- class 类中需要注意的地方

    from django.db import models class Person(models.Model):     name = models.CharField(max_length=30) ...

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

    弄了好久,今天终于把在VC中的对话框类之间传递参数的问题解决了,很开心,记录如下: 1. 我所建立的工程是一个基于MFC对话框的应用程序,一共有三个对话框,第一个对话框为主对话框,所对应的类为CTMD ...

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

    Scala中的类 摘要: 在本篇中,你将会学习如何用Scala实现类.如果你了解Java或C++中的类,你不会觉得这有多难,并且你会很享受Scala更加精简的表示法带来的便利.本篇的要点包括: 1. ...

  6. python中的面向对象学习以及类的继承和继承顺序

    继承 首先编写一串关于类的代码行: __author__ = "Yanfeixu" # class People: 经典类不用加(object) class People(obje ...

  7. Java学习笔记(七)——获取类中方法的信息,java的LinkedList

    [前面的话] 在实际项目中学习知识总是最快和最有效的,既能够较好的掌握知识,又能够做出点东西,还是简单的知识总结,最近一直在总结笔记,写的东西还是比较水,希望慢慢可以写出一些干货. 学习过程中的小知识 ...

  8. Cocos2d-x 3.1.1 学习日志2--error:仅仅有静态常量整型数据成员才干够在类中初始化

        今天遇到比較低端的一个问题,就是成员的初始化问题,编译器也无法验证,不同的编译器有些能过有些不能过,我也不知道为什么,总是我们以vs为准吧,以为我们用的环境就是它,话不多说.解决方式例如以下: ...

  9. Effective C++学习笔记:初始化列表中成员列出的顺序和它们在类中声明的顺序相同

    类成员的默认初始化顺序是按照声明顺序进行, 如果使用初始化列表初始化成员变量, 则必须按照成员变量的声明顺序进行; 否则, 在变量之间交替赋值时, 会产生, 未初始化的变量去赋值其他变量; 同时GCC ...

随机推荐

  1. RabbitMQ安装遇到的问题及解决记录

    提示:若是win10 请注意计算机名称不能有中文 安装Rabbit MQ 需要先安装 Erlang 这里下载版本Erlang OTP22.0 http://www.erlang.org/downloa ...

  2. java中报错:problem with class file or dependent class; nested exception is java.lang.NoClassDefFoundError

    今天和往常一样打开项目,竟然报错problem with class file or dependent class; nested exception is java.lang.NoClassDef ...

  3. Java Web项目案例之---登录注册和增删改查(jsp+servlet)

    登录注册和增删改查(jsp+servlet) (一)功能介绍 1.用户输入正确的密码进行登录 2.新用户可以进行注册 3.登录后显示学生的信息表 4.可以添加学生 5.可以修改学生已有信息 6.可以删 ...

  4. 自定义MessageConverter--消息转换器

    我们在进行发送消息的时候,正常情况下消息体为二进制的数据方式进行传输,如果希望内部帮我们进行转换,或者指定自定义的转换器,就需要用到MessageConverter 自定义常用转换器:MessageC ...

  5. DB 分库分表(1):拆分实施策略和示例演示

    DB 分库分表(1):拆分实施策略和示例演示 第一部分:实施策略 1.准备阶段 对数据库进行分库分表(Sharding化)前,需要开发人员充分了解系统业务逻辑和数据库schema.一个好的建议是绘制一 ...

  6. vscode如何使用命令面板

    vscode如何使用命令面板 方法/步骤     首先找到vscode.   进入,打开页面.   找到查看.   打开找到命令面板.   选择打开,可以看到命令.   下拉还有多个,以及快捷键.   ...

  7. P2983 [USACO10FEB]购买巧克力

    P2983 [USACO10FEB]购买巧克力 题解 注意题目开 long long 贪心策略:价格从低到高,买够为止 反证:若剩下的有一个K”,比K小,那么交换,稳赚不赔 所以,在买K之前,所有比他 ...

  8. 【React自制全家桶】七、React实现ajax请求以及本地数据mock

    一.下载axios插件 yarn add axios 二.React的ajax请求代码如何放置 建议放置在生命周期函数之componentDidMount()中 三.ajax之get请求 axios. ...

  9. struts数据回显

    数据回显,必须要用struts标签! Action中: // 进入修改页面 public String viewUpdate() { // 模拟一个对象(先获取一个id,再根据id调用service查 ...

  10. 阶段3 3.SpringMVC·_07.SSM整合案例_08.ssm整合之Spring整合MyBatis框架

    service能供成功的调用到service对象就算是整合成功 如果能把生成的代理对象也存大IOC的容器中.那么ServiceImpl就可以拿到这个对象 做一个注入,然后就可以调用代理对象的查询数据库 ...