前言

只有品尝过奋斗的滋味,才能体会人生的珍贵;只有勤耕不辍,才能不负韶华、不负自己。
上一篇关于C++学习的文章 C++学习——坚持(二)主要学习了面向对象的一些基本概念,这篇文章主要对类和对象进行进一步的探讨。

一:构造函数

1:简单了解

对于编写程序来说,构造函数可谓是很关键的一部分了,在 c++中,为给对象进行初始化,就提供了一种叫做构造函数的机制,实际上就是为成员变量赋初值的。
构造函数是类中特殊的成员函数,属于类的一部分。当给出类的定义时,由程序员来编写构造函数,如果没有编写类的构造函数,就由系统添加一个参数表为空、函数体也为空的构造函数,也就是说任何类都至少有一个构造函数。
声明对象后,可以用运算符 new 为对象进行初始化,这时调用的是所属对象的构造函数。构造函数的作用是完成对象的初始化工作,用来保证对象的初始状态是确定的

2:如何定义构造函数

当我们在定义一个类时,需要为类定义一个相应的构造函数,构造函数的函数名与类名相同,并且没有返回值。同时构造函数允许重载(指在程序的同一范围内声明几个功能相似的同名函数)。

构造函数的声明格式:
构造函数名(即类名)(形参1,形参2,......形参n);
我们假设一个类的成员变量是 X1,X2…Xn,那么在类体外定义构造函数时有以下几种方式:

形式一:
类名::类名(形参1,形参2,…形参n);
{
X1=形参1;
X2=形参2;

Xn=形参n;
}

形式二:、
类名::类名()
{
X1 = 初始化表达式1;
X2 = 初始化表达式2;

Xn = 初始化表达式n;
}

形式三:
类名::类名(形参1,形参2,…形参n):X1(形参1),X2(形参2)…Xn(形参n){}
例如 :使用固定值在初始化列表中为各个变量赋初值
myDate::myDate():year(2022),month(2),day(2){} //三个函数的初始化均在初始化列表中完成
或使用带入的参数值通过初始化列表为各成员变量赋初值
myDate::myDate(int y,int m,intd):year(y),month(m),day(d){}

在编写构造函数时,能够赋初值的地方有两处:一处是在初始化列表中;一处是在构造函数体内部;构造函数中为成员变量初始化时,既不要有重复,也不要有遗漏

3:怎么使用构造函数

当我们创建好一个对象时,系统就会根据创建对象的语句所提供的参数来选择调用哪一个构造函数去初始化该对象,系统在调用构造函数时是不需要程序员控制的。
假如没有提供参数,就调用无参的构造函数,并且类中定义了哪种构造函数,就决定了创建对象时可以使用那种形式。

例如:定义一个myDate类,构造函数如下:
myDate::myDate(int y = 2022,int m = 8,int d = 1)
{
year = y;
month = m;
day = d;
}
//myDate类的三个参数都有默认值

那么我们创建对象时,就可以采用以下形式:

myDate d0; //输出2022/8/1
myDate d1(2021); //输出 2021/8/1
myDate d2(2021,9); //输出 2021/9/1
myDate d3(2021,9,2); //输出 2021/9/2

调用构造函数时,给定的实参从左至右与形参进行匹配,如果实参的个数少于形参的个数,则不足的形参使用默认值进行初始化。

也可以通过构造函数创建对象指针

假设在类体定义一个构造函数:
myDate::myDate(int y,int m,int d)
{
year = y;
month = m;
day = d;
}
则创建对象时,可以采用:
myData *pa = new myDate (2022,8,1);pa ->printDate();

比如我们声明了一个 Clock类,然后定义一个无参构造函数并调用它:

#include <iostream>
#include <iomanip>
using namespace std;
class Clock
{public:Clock();//声明无参构造函数 void showTime();//声明显示时间的成员函数 private:int hour;//声明表示小时的成员变量 int min; //声明表示分钟的成员变量 int sec;//声明表示秒的成员变量
};
Clock::Clock()//类外实现无参构造函数
{hour=0;//初始化过程中直接给成员变量赋值 min=0;sec=0;
}
void Clock::showTime()//类外实现成员函数
{cout<<setw(2)<<setfill('1')<<hour<<":"<<setw(2)<<setfill('2')<<min<<":"<<setw(2)<<setfill('3')<<sec<<endl; //setw(n)函数用于设置字段宽度为 n位;setfill()函数用于填充字符
}
int main(int argc, char** argv)
{Clock clock;//创建Clock对象clock cout<<"clock";clock.showTime();//对象调用成员函数 return 0;
}

输出结果:

同样的,我们定义一个有参构造函数并调用它;

# include <iostream>
# include <iomanip>
using namespace std;
class Clock
{public:Clock(int h,int m,int s);//声明有参构造函数 void showTime();//声明显示时间的成员函数 private:int hour;int min;int sec;} ;Clock::Clock(int h,int m,int s) //类外实现有参构造函数 {hour=h;//将初始值赋给成员变量min=m;sec=s;}void Clock::showTime()//类外声明成员函数 {cout<<hour<<":"<<min<<":"<<sec<<endl;} int main(){Clock clock1(11,11,11);//创建Clock对象 clock1,并传入初值cout<<"clock1"<<" ";clock1.showTime();//clock1调用成员函数showTime(),用于显示时间Clock clock2(22,22,22);//创建Clock对象 clock2,并传入初值cout<<"clock2"<<" ";clock2.showTime();//clock2调用成员函数showTime()return 0;}

输出结果:

那如果是定义一个含有成员对象的类的构造函数并调用呢?
我们举一个常用的学生和日期的例子,先定义一个日期类myDate,然后再定义一个学生类 Student,Student类中存放学生的姓名和出生日期,其中出生日期类的对象作为Student类的成员变量:

# include <iostream>
using namespace std;
class myDate  //创建myDate类
{ public:myDate(int y,int m,int d);void show();private:int year;int month;int day;
};
myDate::myDate(int y,int m,int d):year(y),month(m),day(d)
{   cout<<"myDate类构造函数"<<endl;
}
void myDate::show()
{cout<<"出生日期:"<<year<<"/"<<month<<"/"<<day<<endl;} class Student //创建Student类{public:Student(string n,  int y, int m, int d); void show();private:string name;myDate date;}; //类外实现构造函数
Student::Student(string n,  int y, int m, int d):date(y,m,d)
{cout<<"Student类构造函数"<<endl;name=n;
}
//类外实现show()函数
void Student::show()
{cout<<"姓名:"<<name<<endl;date.show();
}
int main()
{Student stu("lili",2003,1,1); //创建学生对象stustu.show();      //显示学生信息return 0;
}

输出结果:

二:复制构造函数

复制构造函数也是构造函数的一种,它的作用是使用一个已存在的对象去初始化另一个正在创建的对象。

复制构造函数的格式:
类名::类名(const 类名 &) 为了不改变原有的对象,通常用const 来限定。
或者
类名::类名(类名 &)

声明和实现复制构造函数的一般格式:

class 类名
{
public:
类名(形参表); //构造函数
类名(类名 & 对象名); //复制构造函数

};
类名::类名(类名 & 对象名) // 实现复制构造函数
{
函数体
}

需要注意的是,如果类中没有自行定义复制构造函数,那么编译器就会自动生成一个复制构造函数,通常是使得目标对象的每个成员变量都和源对象相等。自动生成的复制构造函数也叫做默认复制构造函数。默认构造函数不一定存在,但是复制构造函数一定存在。

1:调用默认的复制构造函数:

# include <iostream>
using namespace std;
class Complex
{public:double real,imag;Complex(double r,double i)
{real=r;imag=i;
}
};int main()
{Complex c1(10,24);//声明Complex的对象c1并赋值Complex c2(c1);//c1作为c2的参数,并调用默认复制构造函数对c2进行初始化cout<<"c2:("<<c2.real<<","<<c2.imag<<")";
}

输出结果:

从输出结果可以看到没有自行定义一个复制构造函数时,编辑器就会调用默认的复制构造函数对c2初始化,此时c2就成为c1的复制品。

当定义了复制构造函数时,编辑器就不会再生成默认的复制构造函数了

2:调用自定义的复制构造函数:

# include <iostream>using namespace std;class Complex {public:double real, imag;Complex(double r,double i){real = r; imag = i;}Complex(const Complex & c);//复制构造函数的声明}; Complex::Complex(const Complex & c)  //复制构造函数的实现 {real = c.real+1; imag = c.imag-1;cout<<"自定义复制构造函数"<<endl ;}int main() {Complex c1(3, 4);//调用默认的复制构造函数 cout<<"c1: ("<<c1.real<<","<<c1.imag<<")"<<endl;Complex c2(c1);//调用复制构造函数初始化c2cout<<"c2: ("<<c2.real<<","<<c2.imag<<")"<<endl;return 0;
}

或者是在类体内完成复制构造函数的声明和实现:

class Complex
{public:double real, imag;Complex(double r,double i){real = r; imag = i;}Complex(const Complex & c){real = c.real+1; imag = c.imag-1;cout<<"Copy Constructor called"<<endl ;}
};

输出结果:

自动调用复制构造函数的情况有:
1:当用一个对象去初始化本类的另一个对象时,就会调用复制构造函数:
类名 对象名2(对象名1);
类名 对象名2=对象名1;
2:如果函数F的参数是类A的对象,那么调用F的时候,就会调用类A的复制构造函数;
3:如果函数的返回值是类A的对象,那么当函数返回时,会调用类A的复制构造函数。

三:析构函数

析构函数也是成员函数的一种,它的名字和类名相同,类名前面要加一个字符“~”,用于区分构造函数。析构函数没有参数也没有返回值,一个类中有且仅有一个析构函数。若程序中没有定义析构函数,编辑器会自动生成一个函数体为空的默认析构函数,
它的作用是在对象销毁之前,做一个清理善后的工作,保证空间的可利用性。

析构函数会在对象销毁时被调用,举个简单的例子:

# include <iostream>
using namespace std;
class Deom
{public:~Deom(){cout<<"调用析构函数"<<endl;}
};
int main()
{{Deom deo;}//局部变量在超出局部作用域后对象销毁,这时调用析构函数 cout<<"调用完毕"<<endl;
}

当使用new运算符生成对象指针时,编辑器会自动调用本类的析构函数;使用delete删除这个对象时,首先为这个动态对象调用本类的析构函数,然后再释放占用的空间。

using namespace std;
class A
{public:A(){    cout<<"A的构造函数"  <<endl; }~A(){cout<<"A的析构函数"<<endl;}
};
class B
{A *a;//在B中定义一个指针 public:B(){cout<<"B的构造函数"<<endl;a=new A;//指针指向堆区空间 ,使用new生成对象指针时,自动调用本类的构造函数 }~B(){cout<<"B的析构函数" <<endl;delete a;//使用关键字new 创建的对象,用delete来撤销 ;使用delete删除这个对象时,首先调用本类的析构函数,再释放占用的内存。 }
};
int main()
{B b;//B的对象b定义在栈区,出栈之后自动调用本类的析构函数 return 0;
}

假如将“delete a;”注释掉,那么就会出现内存泄露的情况,而析构函数可以清理类中有指针、并且指向堆区空间的成员,释放指针指向的堆区空间,防止内存泄露。

四:this指针

类的每个成员函数中都包含 this这个特殊的指针,this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。它指向当前的对象,通过它可以访问当前对象的所有成员。

在类的成员函数中,当形参的名字和与变量的名字相同时,通常要在成员变量名前面加上“this->”,用于区分成员变量名和形参名。

#include <iostream>
using namespace std;
class Complex
{public:double real,imag;Complex():real(0),imag(0){}Complex(double,double);Complex AddReal();Complex AddImag();void outPut();
};
Complex::Complex(double real,double imag)
{this->real=real; //在Complex带参构造函数中,根据变量前是否使用 “this->”,区分是类的成员变量还是构造函数的参数 this->imag=imag;
}
void Complex::outPut()
{cout<<"("<<real<<","<<imag<<")";
}
Complex Complex::AddReal()
{this->real++; //“this”代表的是函数所作用的对象,这里在进入AddReal() 函数后,“*this ”就是c1,它的类型就是类Complex 。并且修改的是c1的值。 return *this; //返回的是c1对象本身
}
Complex Complex::AddImag()
{this->imag++;return *this;
}
int main()
{Complex c1(1,2),c2,c3;c1.outPut();c2.outPut();c3.outPut();cout<<endl<<"分界线"<<endl;c2=c1.AddReal(); //将c1进入AddReal()函数后的返回对象赋给c2 c1.outPut();c3=c1.AddImag();//将c1进入AddReal()函数后再进入AddImag()的返回对象 赋给c3 c2.outPut();c3.outPut();cout<<endl;return 0;
}

输出结果:

需要注意的是:

  1. this 是 const 指针,它的值是不能被修改的;
  2. this 只能在成员函数内部使用,用在其他地方没有意义,也是非法的;
  3. 只有当对象被创建后 this 才有意义,类的静态成员函数是没有 this 指针的。

总结

山一程,水一程,走过的都是风景。愿你付出有所得,所想皆成真,在这个秋季,收获更好的自己。


初学乍道,如有不足,望前辈们指正,感谢!

C++学习——前进(三)相关推荐

  1. 强化学习(三) - Gym库介绍和使用,Markov决策程序实例,动态规划决策实例

    强化学习(三) - Gym库介绍和使用,Markov决策程序实例,动态规划决策实例 1. 引言 在这个部分补充之前马尔科夫决策和动态规划部分的代码.在以后的内容我会把相关代码都附到相关内容的后面.本部 ...

  2. Java IO流学习总结三:缓冲流-BufferedInputStream、BufferedOutputStream

    Java IO流学习总结三:缓冲流-BufferedInputStream.BufferedOutputStream 转载请标明出处:http://blog.csdn.net/zhaoyanjun6/ ...

  3. 学习java三个技巧要知道!

    java一直是IT行业发展前景非常不错的一门编程语言,学起来是相对有点困难的,尤其是零基础学员,要想学好java技术,一定要知道这三个技巧,来看看下面的详细介绍就知道了. 学习java三个技巧要知道! ...

  4. J2EE学习笔记三:EJB基础概念和知识 收藏

    J2EE学习笔记三:EJB基础概念和知识 收藏 EJB正是J2EE的旗舰技术,因此俺直接跳到这一章来了,前面的几章都是讲Servlet和JSP以及JDBC的,俺都懂一些.那么EJB和通常我们所说的Ja ...

  5. 统计学习的三个招式:模型、策略和算法

    统计学习的三个招式:模型.策略和算法 https://mp.weixin.qq.com/s/12yhAZ79i_ENAdtyOX63lQ 李航老师在统计学习方法中讲到:方法=模型+策略+算法 可以说模 ...

  6. 数据库MYSQL学习系列三

    数据库MYSQL学习系列三 三.MYSQL事务与存储引擎 3.1-数据库事务 什么是事务 一系列有序的数据库操作: o要么全部成功 o要么全部回退到操作前的状态 o中间状态对其他连接不可见 事务的 ...

  7. 【转载】Wireshark基本介绍和学习TCP三次握手

    Wireshark基本介绍和学习TCP三次握手 转载自:http://www.cnblogs.com/TankXiao/archive/2012/10/10/2711777.html 之前写过一篇博客 ...

  8. tensorflow学习笔记(三十二):conv2d_transpose (解卷积)

    tensorflow学习笔记(三十二):conv2d_transpose ("解卷积") deconv解卷积,实际是叫做conv_transpose, conv_transpose ...

  9. ROS与Arduino学习(三)订阅与发布

    ROS与Arduino学习(三)订阅与发布 Tutorial Level:HelloWorld Next Tutorial:用ROS的Cmake编译程序 本节介绍如何在arduino中发布与订阅消息. ...

  10. 【机器学习】SVM学习(三):线性分类器的求解

    [机器学习]SVM学习(三):线性分类器的求解 2016-10-10 19:56 239人阅读 评论(0) 收藏 举报 本文章已收录于:  机器学习知识库  分类: 机器学习(37)  目录(?)[+ ...

最新文章

  1. 哑谜,回文和暴力之美
  2. android 网络编程实现,Android开发使用HttpURLConnection进行网络编程详解【附源码下载】...
  3. [Linux] set dev label(设置分区卷标)
  4. mysql中检索以名字_【MySQL必知必会】第四章 检索数据
  5. QT的QDateTimeAxis类的使用
  6. 关于压缩工具 7z(7-zip) 如何设置压缩算法(选项 -m 的解读)
  7. [react] React的严格模式有什么用处?
  8. QList、QVector、QMap容器类
  9. C++ string , int 之间相互转换
  10. 阶段3 2.Spring_07.银行转账案例_9 基于子类的动态代理
  11. 房价增幅或于年底见顶
  12. 【学习笔记】浅识差分隐私
  13. 浏览器网页无法打开麦克风、摄像头
  14. 2004年11月网络工程师级试题
  15. EMLOG模板山河网站主题分享
  16. jquery嘎嘎嘎嘎嘎嘎
  17. 光谱数据计算CIE值(三刺激值、CIE1931、CIE1976、CCT)软件人品大家自己看
  18. 针对safecast数据集的数据清洗
  19. Memcached和Redis数据缓存系统
  20. 《GhostXP_SP2电脑公司特别版_v8.5》

热门文章

  1. 真三维立体显示技术分类、特点及展望
  2. 姚班智班齐上阵,竞赛高手聚一堂,这是什么神仙编程大赛?
  3. 软件工程实验报告七 UML建模-对象模型(包图、类图)
  4. 【原创】【算法】三点定位简述
  5. 1实心聚乙烯和发泡聚乙烯的区别
  6. 这里有一份关于英文怎么翻译的方法,快快学起来吧
  7. python转化极坐标_数学高考与python
  8. android Launcher3 动态设置一个快捷图标(添加快捷图标)
  9. 双反斜杠无法访问其它计算机(0x80004005)
  10. 超声波声音和速度关系对测试距离的影响