C++学习5-面向对象编程基础(构造函数、转换构造、静态数据成员、静态成员函数、友元)

知识点学习

const作用

C语言的const限定符的含义为“一个不能改变值的变量”,C++的const限定符的含义为“一个有类型描述的常量”;

const修饰指向的实体类型被称为常量指针,限定指针必须指向一个地址

const int * p = &a;

//与上一条语句等价

int const * dp = &b;

const修饰指针*cp被称为 指针常量,限定指针值不能修改;

int * const cp = &b

const加在函数前面是声明该函数会返回一个常量类型的函数,像这样使用const的成员函数被称为常量成员函数

:返回类型、参数列表和函数名都得与类内部的声明保持一致。如果成员被声明成常量成员函数,那么它的定义也必须在参数列表后明确指定const ;

const string & getName()  {  return name; }

const加在函数名后面表示是this是一个指向常量的指针

string & getName() const {  return name; }

构造函数

类创建对象时需要对对象初始化,但初始化任务只有成员函数完成,因此在类中必须定义一个具有初始化功能的成员函数。每当调用一个对象时,就调用这个成员函数实现初始化;

带参构造函数初始化的两种方式

函数体外初始化: 当进行函数体外初始化时,初始化赋值的优先顺序由变量在类内定义中的出现顺序决定的;初始化值列表中初始值的前后位置关系不会影响实际的初始化顺序;

函数体内初始化:赋值优先级分别为左向右赋值;(默认实参值必须从右向左顺序声明。在默认形参值的右边不能有非默认形参值的参数,因为调用时实参取代形参是从左向右的顺序)

实例代码

#include "stdafx.h"

class Ctest
{
public:
//构造函数体外初始化
Ctest(int i, int j) :m_NumberA(i),m_NumberB(j){};
//构造函数体内初始化
Ctest(int i, int j, int k = 0)
{
m_NumberA = i;
m_NumberB = j;
}
protected:
private:
int m_NumberA;
int m_NumberB;
}; int _tmain(int argc, _TCHAR* argv[])
{
Ctest obj(97, 100);
return 0;
}

必须在初始化列表中初始化的三种情况

构造函数初始化时必须采用初始化列表一共有三种情况:

  • 常量类型,必须在初始化列表中初始化
  • 引用类型,必须在初始化列表中初始化
  • 类类型(没有无参构造时),必须在初始化列表中初始化

总结如下:

1、const成员:常量,不能被改变,定义的同时必须初始化

2、引用成员:&,别名,与目标共享地址,定义的同时必须初始化

3、没有默认构造函数供系统自动调用:

(1)对象成员:A类的成员是B类的对象,在构造A类时需对B类的对象进行构造,当B类没有默认构造函数时需要在A类的构造函数初始化列表中对B类对象初始化

(2)类的继承:派生类在构造函数中要对自身成员初始化,也要对继承过来的基类成员进行初始化,当基类没有默认构造函数的时候,通过在派生类的构造函数初始化列表中调用基类的构造函数实现

实例代码

#include "stdafx.h"
class CTemp
{
public:
CTemp(int) {};
}; class CTest
{
public:
CTest(int a,int b,int c)
:m_cNum(900), m_temp(m_a), m_CTemp(500) //真正的初始化
{
m_a = a;
m_b = b;
m_c = c;
}
CTest(int a, int b);
private:
int m_a,m_b,m_c;
//常量类型,必须在初始化列表中初始化
const int m_cNum;
//引用类型,必须在初始化列表中初始化
int & m_temp;
//类类型(没有无参构造时),必须在初始化列表中初始化
CTemp m_CTemp;
}; CTest::CTest(int a, int b = 900)
:m_a(a), m_c(m_b), m_b(100), //真正的初始化
m_cNum(900), m_temp(m_a), m_CTemp(m_a)
{
m_a = 100;
m_b = 300;
} int main()
{
CTest test(1,2); return 0;
}

转换构造函数

将一个其他类型的数据转换成一个类的对象,当一个构造函数只有一个参数,而且该参数又不是本类的const引用时,这种构造函数称为转换构造函数

转换构造函数实例代码

#include "stdafx.h"

class CNumber
{
public:
CNumber(int a) :m_nNum(a){}
CNumber(int a, int b) :m_nNum(a) {}
private:
int m_nNum;
}; int main()
{
CNumber objA(55); //正常调用
objA = 80; //把其他类型的变量直接赋值给对象时,会引发隐式转换
//会尝试把该变量转换成 CNumber 类类型。
//把该变量当做参数,去匹配 CNumber 类中是否有合适的构造函数
//如果匹配成功,就会创建一个临时对象
//并把临时对象的值,逐个赋值给 objA 中的数据成员 return 0;
}

在单个参数的构造函数之前加上explicit关键字,就会阻止转换构造函数,同时也会在数值赋值给类对象时,编译器报错。使用=号无法赋值给类对象;

代码与上个例子相同,但是加了explicit关键字

explicit关键字实例代码

#include "stdafx.h"

class CNumber
{
public:
explicit CNumber(int a) :m_nNum(a){}
CNumber(int a, int b) :m_nNum(a) {}
private:
int m_nNum;
}; int main()
{
CNumber objA(55); //正常调用
objA = 80; //把其他类型的变量直接赋值给对象时,会引发隐式转换
//会尝试把该变量转换成 CNumber 类类型。
//把该变量当做参数,去匹配 CNumber 类中是否有合适的构造函数
//如果匹配成功,就会创建一个临时对象
//并把临时对象的值,逐个赋值给 objA 中的数据成员 return 0;
}

缺省构造函数

每一个类中必须有一个构造函数,没有构造函数就不能创建任何对象,若未定义一个类的构造函数,则C++提供了一个缺省的构造函数,该缺省构造函数是一个无参数的构造函数,仅仅负责创建对象,而不做任何初始化工作;

在用缺省构造函数创建对象时,如果创建的是全局对象或静态对象,初始化值默认为0,局部对象的初始化值默认为随机数。

注意:一个空类的大小为1个字节;

未自定义构造函数或析构函数时,编译器默认会生成6个隐式成员函数;

Empty(); //默认构造函数

Empty(const Empty & ); //默认拷贝构造函数

~Empty(); //默认析构函数

Empty & operator=( const Empty &); //默认赋值运算符

Empty * operator & (); //取址运算符

const Empty * operator & () const; //取址运算符const

析构函数

析构函数是类的一种特殊的成员函数,类定义的构造函数在对象之外分配一段堆内存空间,撤销时,由析构函数负责对堆内存释放;

注意:析构函数以调用构造函数相反的顺序被调用;

拷贝构造函数

拷贝构造函数用于由一个已知的对象创建一个新对象,格式如下:

类名::拷贝构造函数(类名 & 引用名)

1.拷贝构造函数通常发生于定义一个对象,或者往函数传递类类型的参数的时候,或者当返回值为一个对象的时候,一般在上述情况下,会调用拷贝对象进行初始化一个对象。

2.拷贝构造函数的参数如果不是引用,那么在调用拷贝构造的时候,发生了类类型参数传递,这个时候又会调用拷贝构造,那么就会无限递归下去。

3.使用引用传递参数,会避免调用拷贝构造,为了防止被复制的对象变样,这个时候使用const引用类类型;

const限定拷贝构造函数格式如下

类名::拷贝构造函数(const 类名 & 引用名)

通常构造函数只在对象创建时被调用,而拷贝构造函数则在以下3种情况时被调用。

当使用类的一个对象去初始化该类的另一个新对象时。

如果函数的形参是类的对象,那么当调用该函数时拷贝构造函数也会被调用。

如果函数的返回值是类的对象,那么函数执行完成返回调用者时。

深拷贝与浅拷贝问题

使用拷贝函数需要注意一个细节,就是深拷贝与浅拷贝的问题,如果未生成自定义拷贝构造函数很容易会引起内存提前释放的问题。

  • 浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。

例如:系统默认的拷贝构造函数,会将对象内的成员直接赋值,会造成两个指针存储同一个地方的地址。当析构函数释放了某一个指针所指向的动态资源后,另一个指针就没法用了;

  • 深拷贝是为新对象从堆中再申请出一片空间,使得两个对象相互独立;

实例代码

以下代码是一个学生类,实现了系统默认拷贝函数然后通过构造函数的重载进行深拷贝与浅拷贝进行对比;只使用浅拷贝时会出现报错,而将浅拷贝代码注释再使用深拷贝则不会出现问题;

#include "stdafx.h"
#include <string.h> class CStudent
{
public:
CStudent() :m_nAge(0), m_nID(0), m_szName(NULL) {};
CStudent(const CStudent & obj);
//构造函数的重载实现方式
CStudent(char * szName,int nAge,int nID):m_nAge(nAge),m_nID(nID)
{
//深拷贝
unsigned int nSize = strlen(szName);
m_szName = new char[nSize + 1];
strcpy_s(m_szName,strlen(szName)+1,szName);
} ~CStudent()
{
delete[] m_szName;
}; private:
char * m_szName; //姓名
int m_nAge; //年龄
int m_nID; //学号
}; //拷贝构造函数
CStudent::CStudent(const CStudent & obj)
{
//浅拷贝
m_szName = obj.m_szName;
m_nAge = obj.m_nAge;
m_nID = obj.m_nID;
//深拷贝
//unsigned int nSize = strlen(szName);
//m_szName = new char[nSize + 1];
//strcpy_s(m_szName, strlen(szName) + 1, szName);
}
int main()
{ CStudent obj("aaaa", 15, 1);
CStudent obj_copy(obj); //执行拷贝构造函数; return 0;
}

静态成员

静态成员包括静态数据成员与静态成员函数组成。在类内使用static关键字进行定义,在类体外进行初始化;

静态成员的出现则是是为了避免过多的使用全局变量,解决多个对象之间数据共享的安全性问题,。

注意:静态数据成员只能被静态成员函数调用,静态成员函数也只能调用静态数据成员;

  • 使用方式

<类名>::<静态成员函数名>(<参数表>);

<对象名>.<静态成员函数名>(<参数表>)

静态成员使用实例代码

#include "stdafx.h"
#include <iostream>
using namespace std; class CCounter
{
public:
void setcount(int i) {
m_nCount = i; m_nCount1 = i;
}
void showcount() { cout << m_nCount <<" "<< m_nCount1 << endl; }
private:
static int m_nCount; //在类体内说明静态数据
int m_nCount1;
}; int CCounter::m_nCount = 10; //在类体外定义静态数据 int main()
{
CCounter objA, objB;
objA.showcount();
objB.showcount();
objA.setcount(15);
objA.showcount();
objB.showcount();
return 0;
}

静态成员函数使用实例代码

#include "stdafx.h"
#include <iostream>
using namespace std;
class CCount
{
public:
CCount(){} static void showCount()
{
//this -> m_nCount;
cout << m_nCount1<<endl;
}
private:
int m_nCount;
static int m_nCount1;
};
int CCount::m_nCount1 = 99; //静态函数的应用场景 (单例模式:类对象只能被创建1次)
class Single
{
public: static Single * init()
{
if (m_obj == nullptr)
{
m_obj = new Single();
}
return m_obj;
} void print() { cout << "我的地址是:" << this<<"\n"<<endl; };
private:
Single() {}
static Single * m_obj;
};
Single * Single::m_obj = nullptr; int main()
{
//使用静态成员函数通过作用域进行调用
CCount::showCount();
CCount objA;
objA.showCount();
//也可以定义指针对象,以指针形式调用
Single * obj1 = Single::init();
obj1->print();
return 0;
}

友元

当程序需要大量且频繁的操作类中的数据时,会造成程序效率变慢;

类可以允许其他类或者类外函数访问它的非公有成员,方法是在类内定义一个外部的函数前加上friend关键字令其他类或者函数成为友元,从而达到访问类内非公有成员的目的;

需要注意一下两个概念

1、友元是无法被继承的,也不具备传递性;

2、构造函数、析构函数、拷贝构造函数都是特殊的成员函数,友元则不是成员函数,只是普通的函数被声明为友元;

3、友元并非可以直接访问类的所有成员,而是通过类的对象访问类中的所有成员;

友元函数存在的作用就在于更方便的访问类中的所有成员,不需要调用成员函数,提高了程序运行的效率

友元函数的使用格式为:

	friend <返回类型> <函数名>;

友元类的使用格式为:

	friend class <类名>;

友元成员函数的使用格式为:

	friend 返回类型 类名::函数(形参)。

友元函数实例代码

#include "stdafx.h"

class MyClass
{ private:
int m_param;
int m_Num;
public:
int getParam() const { return m_param; }
void setParam(int val) { m_param = val; }
int getNum() const { return m_Num; }
void setNum(int val) { m_Num = val; }
//声明友元函数只要在前面加上friend关键字就可以了
friend void fun();
};
//如果是友元函数是可以调用类中的私有成员变量的
void fun()
{
MyClass my;
int nNum = my.getParam();
my.setParam(199); int nNum2 = my.m_param;
my.m_param = 100;
}
//如果不是友元函数是无法调用类中的私有成员变量的
void fun2()
{
MyClass my;
int nNum2 = my.m_param;
my.m_param = 100;
}
int main()
{
MyClass my;
fun();
return 0;
}
  • 友元类

类还可以把其他类定义成友元,如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。

当声明一个类A为另一个类B的友元时,友元A中的所有成员函数都是类B的友元函数;

友元类实例代码

#include "stdafx.h"
#include <iostream>
using namespace std; class CCounter2
{
public:
CCounter2(){ m_nCount = 2; };
~CCounter2(){}; int getCount() const { cout << "我是友元成员函数" << endl; return m_nCount; }
void setCount(int val) { m_nCount = val; }
protected:
int m_nCount;
//定义CFriendClass类为CCounter2的友元类
friend class CFriendClass;
}; //友元类
class CFriendClass
{
public:
CFriendClass(){};
~CFriendClass(){}; void fun();
void fun1();
}; void CFriendClass::fun()
{
//调用成员函数
CCounter2 counter;
//counter.m_nCount;
counter.getCount();
} void CFriendClass::fun1()
{ //调用CCounter2的私有成员
CCounter2 counter;
cout <<"我是友元私有成员:" << counter.m_nCount << endl;
} int main()
{
CFriendClass fri;
fri.fun();
fri.fun1();
return 0;
}
  • 友元成员函数

    除了令整个类成为友元外,还可以只为类中的某个成员函数提供访问权限;
#include "stdafx.h"
#include <iostream>
using namespace std; class CCounter; //注意互相包含的问题!!
class CShow {
public:
void show(CCounter obj);
void show2(CCounter obj);
};
class CCounter {
public:
CCounter() {}
CCounter(int nNum) { m_nCount = nNum; }
void showcount() { cout << m_nCount << endl; }
private:
int m_nCount;
friend void CShow::show(CCounter obj);
};
void CShow::show(CCounter obj) {
cout << obj.m_nCount << endl;
}
void CShow::show2(CCounter obj) {
//cout << obj.m_nCount << endl;
//不是友元函数,不能直接调用私有数据成员,编译前就会报错
} int main()
{
CCounter counter(6);
CShow show;
show.show(counter);
show.show2(counter); return 0;
}

建议完成《C++ primer》的练习

第七章 类

建议完成本章的练习:7.4, 7.5, 7.15, 7.19, 7.22, 7.23, 7.24, 7.27, 7.28, 7.29, 7.32, 7.33, 7.58

上一篇:python对象销毁(垃圾回收)


下一篇:c# thread pause example