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
C++学习5-面向对象编程基础(构造函数、转换构造、静态数据成员、静态成员函数、友元)的更多相关文章
- 大数据技术之_16_Scala学习_04_函数式编程-基础+面向对象编程-基础
第五章 函数式编程-基础5.1 函数式编程内容说明5.1.1 函数式编程内容5.1.2 函数式编程授课顺序5.2 函数式编程介绍5.2.1 几个概念的说明5.2.2 方法.函数.函数式编程和面向对象编 ...
- Python学习-第三天-面向对象编程基础
Python学习-第三天-面向对象编程基础 类和对象 简单的说,类是对象的蓝图和模板,而对象是类的实例.这个解释虽然有点像用概念在解释概念,但是从这句话我们至少可以看出,类是抽象的概念,而对象是具体的 ...
- JAVA学习(五):Java面向对象编程基础
Java面向对象编程基础 面向对象(Object oriented programming,OOP)技术是一种强有力的软件开发方法,它採用数据抽象与信息隐藏技术,来使软件开发简单化,以达到代码重用的目 ...
- [.net 面向对象编程基础] (7) 基础中的基础——流程控制语句
[.net 面向对象编程基础] (7) 基础中的基础——流程控制语句 本来没有这一节的内容,后来考虑到既然是一个系列文章,那么就尽可能写的详细一些,本节参考了网上朋友所写的例子,为的是让更多小伙伴学习 ...
- [.net 面向对象编程基础] (14) 重构
[.net 面向对象编程基础] (14) 重构 通过面向对象三大特性:封装.继承.多态的学习,可以说我们已经掌握了面向对象的核心.接下来的学习就是如何让我们的代码更优雅.更高效.更易读.更易维护.当然 ...
- [.net 面向对象编程基础] (19) LINQ基础
[.net 面向对象编程基础] (19) LINQ基础 上两节我们介绍了.net的数组.集合和泛型.我们说到,数组是从以前编程语言延伸过来的一种引用类型,采用事先定义长度分配存储区域的方式.而集合是 ...
- [.net 面向对象编程基础] (15) 抽象类
[.net 面向对象编程基础] (15) 抽象类 前面我们已经使用到了虚方法(使用 Virtual修饰符)和抽象类及抽象方法(使用abstract修饰符)我们在多态一节中说到要实现类成员的重写必须定义 ...
- [.net 面向对象编程基础] (16) 接口
[.net 面向对象编程基础] (16) 接口 关于“接口”一词,跟我们平常看到的电脑的硬件“接口”意义上是差不多的.拿一台电脑来说,我们从外面,可以看到他的USB接口,COM接口等,那么这些接口的目 ...
- [.net 面向对象编程基础] (17) 数组与集合
[.net 面向对象编程基础] (17) 数组与集合 学习了前面的C#三大特性,及接口,抽象类这些相对抽象的东西以后,是不是有点很累的感觉.具体的东西总是容易理解,因此我们在介绍前面抽象概念的时候,总 ...
随机推荐
- MT【64】2017联赛一试不等式的一个加强练习
已知$x_1,x_2,x_3\ge0,x_1+x_2+x_3=1$求 $$(x_1+3x_2+5x_3)(x_1+\frac{1}{3}x_2+\frac{1}{5}x_3)(x_1+x_3+3x_2 ...
- Python函数绘图
最近看数学,发现有时候画个图还真管用,对理解和展示效果都不错.尤其是三维空间和一些复杂函数,相当直观,也有助于解题.本来想用mathlab,下载安装都太费事,杀鸡不用牛刀,Python基本就能实现.下 ...
- 自学Linux Shell9.2-基于Red Hat系统工具包存在两种方式之一:RPM包
点击返回 自学Linux命令行与Shell脚本之路 9.2-基于Red Hat系统工具包存在两种方式之一:RPM包 本节主要介绍基于Red Had的系统(测试系统centos) 1. 工具包存在两种方 ...
- [luogu1373]小a和uim之大逃离【动态规划】
传送门:https://www.luogu.org/problemnew/show/P1373 定义状态是:\(f[i][j][h][0..1]\)表示在\([i,j]\)两个人相差为h,让某一个人走 ...
- How to Add Trust Sites into IE before IE10 through Group Policy
Due to IE10 published, I'll conclude the methods that how to add trust sites in to IE of the version ...
- 2018 ACM 网络选拔赛 南京赛区
A. An Olympian Math Problem #include <cstdio> #include <cstdlib> #include <cmath> ...
- NCBI上查看SNP位点在哪个基因座上(locus)
首先,进入NCBI的主页网站:https://www.ncbi.nlm.nih.gov/variation/view/ 进入后,在下图红色框框位置输入目的SNP,比如rs608139 输完后,出现如下 ...
- GO语言的进阶之路-初探GO语言
GO语言的进阶之路-初探GO语言 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.为什么我们需要一门新语言 Go语言官方自称,之所以开发Go 语言,是因为“近10年来开发程序之难 ...
- 设计模式---单一职责模式之装饰模式(Decorator)
前提:"单一职责"模式 在软件组件的设计中,如果责任划分的不清晰,使用继承,得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码,这时候的关键是划清责任 典型模式(表现 ...
- 什么是cap
cap理论是分布式系统中非常重要的一个理念 什么是cap理论: Consistency一致性 Availability可用性 Partition-tolerance分区容忍性 CP: 高一致性C和分区 ...