超女选秀的例子我们玩了很久,为了学习的需要,暂时离开美眉们,我将采用实际项目开发的例子来讲解类的更多知识。

在C语言基础知识中已学习过文件操作,在实际开发中,为了提高效率,我会把文件操作封装成一个类,类的声明如下:

// 文件操作类声明
class CFile
{
private:
FILE *m_fp; // 文件指针
bool m_bEnBuffer; // 是否启用缓冲区,true-启用;false-不启用 public:
CFile(); // 类的构造函数
CFile(bool bEnBuffer); // 类的构造函数 ~CFile(); // 类的析构函数 void EnBuffer(bool bEnBuffer=true); // 启、禁用缓冲区 // 打开文件,参数与fopen相同,打开成功true,失败返回false
bool Open(const char *filename,const char *openmode); // 调用fprintf向文件写入数据
void Fprintf(const char *fmt, ... ); // 调用fgets从文件中读取一行
bool Fgets(char *strBuffer,const int ReadSize); // 关闭文件指针
void Close();
};

一、类成员的访问权限

C++通过 public、protected、private三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受保护的、私有的,被称为成员访问限定符。所谓访问权限,就是类外面的代码访问该类中成员权限。

在类的内部,即类的成员函数中,无论成员被声明为 public、protected 还是private,都是可以互相访问的,没有访问权限的限制。

在类的外部(定义类的代码之外),只能通过对象访问public的成员,不能访问 private、protected属性的成员。

本节重点介绍 public 和 private,protected 将在以后介绍。

private 后面的成员都是私有的,如m_fp和m_bEnBuffer,直到有 public出现才会变成共有的;public 之后再无其他限定符,所以 public后面的成员都是共有的。

private关键字的作用在于更好地隐藏类的内部实现,该向外暴露的接口(能通过对象访问的成员)都声明为public,不希望外部知道、或者只在类内部使用的、或者对外部没有影响的成员,都建议声明为private。

声明为 private 的成员和声明为 public 的成员的次序任意,既可以先出现 private部分,也可以先出现 public 部分。如果既不写 private 也不写 public,就默认为private。

在一个类体中,private 和 public可以分别出现多次。每个部分的有效范围到出现另一个访问限定符或类体结束时(最后一个右花括号)为止。

您可能会说,将成员变量全部设置为 public 省事,确实,这样做 99.9%的情况下都不是一种错误,我也不认为这样做有什么不妥;但是,将成员变量设置为private 是一种软件设计规范,尤其是在大中型项目中,还是请大家尽量遵守这一原则。

二、成员变量的命名

成员变量大都以m_开头,这是约定成俗的写法,不是语法规定的内容。以m_开头既可以一眼看出这是成员变量,又可以和成员函数中的参数名字区分开。

例如成员函数EnBuffer的函数体如下:

// 启、禁用缓冲区
void CFile::EnBuffer(bool bEnBuffer)
{
m_bEnBuffer=bEnBuffer;
}

三、构造函数

在CFile类的声明中,有一些特殊的成员函数CFile(),它就是构造函数(constructor)。

  CFile();   // 类的构造函数
CFile(bool bEnBuffer); // 类的构造函数

构造函数的名字和类名相同,没有返回值,不能被显式的调用,而是在创建对象时自动执行。

构造函数具备以下特点:

1)构造函数必须是 public 属性。

2)构造函数没有返回值,因为没有变量来接收返回值,即使有也毫无用处,不管是声明还是定义,函数名前面都不能出现返回值类型,即使是void 也不允许。

3)构造函数可以有参数,允许重载。一个类可以有多个重载的构造函数,创建对象时根据传递的参数来判断调用哪一个构造函数。

4)构造函数在实际开发中会大量使用,它往往用来做一些初始化工作,对成员变量进行初始化等,注意,不能用memset对整下类进行初始化。

示例

CFile::CFile()   // 类的构造函数
{
m_fp=0;
m_bEnBuffer=true;
} CFile::CFile(bool bEnBuffer) // 类的构造函数
{
m_fp=0;
m_bEnBuffer=bEnBuffer;
}

四、析构函数

在CFile类的声明中,还有一个特殊的成员函数~CFile(),它就是析构函数(destructor)。

~CFile();   // 类的析构函数

析构函数的名字在类的名字前加~,没有返回值,但可以被显式的调用,在对象销毁时自动执行,用于进行清理工作,例如释放分配的内存、关闭打开的文件等,这个用途非常重要,可以防止程序员犯错。

析构函数具备以下特点:

1)构造函数必须是 public 属性的。

2)构造函数没有返回值,因为没有变量来接收返回值,即使有也毫无用处,不管是声明还是定义,函数名前面都不能出现返回值类型,即使是void 也不允许。

3)析构函数不允许重载的。一个类只能有一个析构函数。

CFile::~CFile()   // 类的析构函数
{
Close(); // 调用Close释放资源
}

五、C++程序也很优雅

很多人说C/C++程序很烦锁,python程序很优雅,说这话人的很荒谬,那是因为他C/C++并不了解,只要我们愿意,可以写出和python一样优雅简洁的代码,在book210.cpp中,main函数的代码极为精简。

示例(book210.cpp)

/*
* 程序名:book210.cpp,此程序演示用C++类的更多知识。
* 作者:C语言技术网(www.freecplus.net) 日期:20190525
*/
#include <stdio.h>
#include <string.h>
#include <stdarg.h> // 文件操作类声明
class CFile
{
private:
FILE *m_fp; // 文件指针
bool m_bEnBuffer; // 是否启用缓冲区,true-启用;false-不启用 public:
CFile(); // 类的构造函数
CFile(bool bEnBuffer); // 类的构造函数 ~CFile(); // 类的析构函数 void EnBuffer(bool bEnBuffer=true); // 启、禁用缓冲区 // 打开文件,参数与fopen相同,打开成功true,失败返回false
bool Open(const char *filename,const char *openmode); // 调用fprintf向文件写入数据
void Fprintf(const char *fmt,... ); // 调用fgets从文件中读取一行
bool Fgets(char *strBuffer,const int ReadSize); // 关闭文件指针
void Close();
}; int main(int argc,char *argv[])
{
if (argc !=2) { printf("请输入待打开的文件名。\n"); return -1; } CFile File; if (File.Open(argv[1],"r")==false) { printf("File.Open(%s)失败。\n",argv[1]); return -1; } char strLine[301]; while (true)
{ // 从文件中读取每一行
if (File.Fgets(strLine,300)==false) break; printf("%s",strLine); // 把从文件中读到的内容显示到屏幕
}
} CFile::CFile() // 类的构造函数
{
m_fp=0;
m_bEnBuffer=true;
} CFile::CFile(bool bEnBuffer) // 类的构造函数
{
m_fp=0;
m_bEnBuffer=bEnBuffer;
} // 关闭文件指针
void CFile::Close()
{
if (m_fp!=0) fclose(m_fp); // 关闭文件指针
m_fp=0;
} CFile::~CFile() // 类的析构函数
{
Close(); // 调用Close释放资源
} // 启、禁用缓冲区
void CFile::EnBuffer(bool bEnBuffer)
{
m_bEnBuffer=bEnBuffer;
} // 打开文件,参数与fopen相同,打开成功true,失败返回false
bool CFile::Open(const char *filename,const char *openmode)
{
Close(); // 打开新的文件之前,如果已经打开了文件,关闭它。 if ( (m_fp=fopen(filename,openmode)) == 0 ) return false; return true;
} // 调用fprintf向文件写入数据
void CFile::Fprintf(const char *fmt,...)
{
if ( m_fp == 0 ) return; va_list ap;
va_start(arg,ap);
vfprintf(m_fp,fmt,ap);
va_end(ap); if ( m_bEnBuffer == false ) fflush(m_fp);
} // 调用fgets从文件中读取一行
bool CFile::Fgets(char *strBuffer,const int ReadSize)
{
if ( m_fp == 0 ) return false; memset(strBuffer,0,ReadSize); if (fgets(strBuffer,ReadSize,m_fp) == 0) return false; return true;
}

book210运行的效果就是把文件的内容一行一行的显示出来,类型linux系统的cat命令。

六、类的其它知识

关于类的其它知识,包括this指针、static静态成员、友元等内容,意义不大,我不介绍了,时间太宝贵,有太多重要的知识要学习,没必要把时间浪费在这些不痛不痒又没什么实用价值的知识点上,大家以后有时间了再看也行。

七、可变参数

我们已经介绍过printf、fprintf、sprintf、snprintf函数,它们是一组功能相似的函数,并且有一个共同点,就是函数的参数列表是可以变化的。

函数的声明如下:

int printf(const char *format, ...);        // 格式化输出到屏幕
int fprintf(FILE *stream, const char *format, ...); // 格式化输出到文件
int sprintf(char *str, const char *format, ...); // 格式化输出到字符串
int snprintf(char *str, size_t size, const char *format, ...); // 格式化输出指定长度的内容到字符串

在实际开发中,我们的自定义函数也会用到可变参数,实现类似上述函数的功能,例如CFile类的Fprintf成员函数。

C语言采用va_start宏、va_end宏和一系列函数来实现可变参数功能。

void CFile::Fprintf(const char *fmt,...)
{
if ( m_fp == 0 ) return; va_list ap;
va_start(arg,ap);
vfprintf(m_fp,fmt,ap);
va_end(ap); if ( m_bEnBuffer == false ) fflush(m_fp);
}

以CFile类的Fprintf成员函数为例。

void CFile::Fprintf(const char *fmt,...);     // 可变参数自定义函数的声明方法

va_list指针、va_start宏、va_end宏用于分析参数,难以理解,大家会用就行,我不详细介绍。

  va_list ap;
va_start(ap,fmt);
vfprintf(m_fp,fmt,ap);
va_end(ap);

vfprintf函数把宏分析的结果输出到文件,还有一系列功能相似的函数,声明如下:

// 输出的屏幕
int vprintf(const char *format, va_list ap);
// 输出到文件
int vfprintf(FILE *stream, const char *format, va_list ap);
// 输出到字符串
int vsprintf(char *str, const char *format, va_list ap);
// 输出到字符串,第二个参数指定了输出结果的长度,类似snprintf函数。
int vsnprintf(char *str, size_t size, const char *format, va_list ap);

八、课后作业

1)编写示例程序,测试类的类成员的访问权限。

2)编写示例程序,测试类的构造函数和它的重载,采用gdb跟踪构造函数的执行过程。

3)编写示例程序,测试类的析构函数,采用gdb跟踪析构造的执行过程。

4)编写示例程序,实现printf、sprintf和snprintf函数的功能,函数的声明如下:

int myprintf(const char *format, ...);
int mysprintf(const char *format, ...);
int mysnprintf(const char *format, ...);

5)类定义包括成员变量和成员函数的声明以及成员函数的定义,在实际开发中,我们通常将公共类的声明放在头文件中(如_public.h),成员函数的定义放在程序文件中(如_public.cpp),请按这种方式修改book210.cpp程序,增加_public.h和_public.cpp程序,修改makefile。

九、版权声明

C语言技术网原创文章,转载请说明文章的来源、作者和原文的链接。

来源:C语言技术网(www.freecplus.net)

作者:码农有道

如果文章有错别字,或者内容有错误,或其他的建议和意见,请您留言指正,非常感谢!!!

C++类的详解的更多相关文章

  1. qml学习笔记(二):可视化元素基类Item详解(上半场anchors等等)

    原博主博客地址:http://blog.csdn.net/qq21497936本文章博客地址:http://blog.csdn.net/qq21497936/article/details/78516 ...

  2. UML类图详解_关联关系_一对多

    对于一对多的示例,可以想象一个账户可以多次申购.在申购的时候没有固定上限,下限为0,那么就可以使用容器类(container class)来搞,最常见的就是vector了. 下面我们来看一个“一对多” ...

  3. UML类图详解_关联关系_多对一

    首先先来明确一个概念,即多重性.什么是多重性呢?多重性是指两个对象之间的链接数目,表示法是“下限...上限”,最小数据为零(0),最大数目为没有设限(*),如果仅标示一个数目级上下限相同. 实际在UM ...

  4. Android中Application类的详解:

    Android中Application类的详解: 我们在平时的开发中,有时候可能会须要一些全局数据.来让应用中的全部Activity和View都能訪问到.大家在遇到这样的情况时,可能首先会想到自定义一 ...

  5. Delphi中的线程类 - TThread详解

    Delphi中的线程类 - TThread详解 2011年06月27日 星期一 20:28 Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本 ...

  6. UML简单介绍—类图详解

    类图详解 阅读本文前请先阅读:UML简单介绍—类图这么看就懂了 1.泛化关系 一个动物类: /** * 动物类 */ public class Animal { public String name; ...

  7. Properties类使用详解

    Java Properties类使用详解   概述 Properties 继承于 Hashtable.表示一个持久的属性集,属性列表以key-value的形式存在,key和value都是字符串. Pr ...

  8. 在java poi导入Excel通用工具类示例详解

    转: 在java poi导入Excel通用工具类示例详解 更新时间:2017年09月10日 14:21:36   作者:daochuwenziyao   我要评论   这篇文章主要给大家介绍了关于在j ...

  9. MFC中文件对话框类CFileDialog详解及文件过滤器说明

    当前位置 : 首页 » 文章分类 :  开发  »  MFC中文件对话框类CFileDialog详解及文件过滤器说明 上一篇 利用OpenCV从摄像头获得图像的坐标原点是在左下角 下一篇 Word中为 ...

  10. moviepy音视频剪辑:视频剪辑基类VideoClip详解

    ☞ ░ 前往老猿Python博文目录 ░ 一.概述 在<moviepy音视频剪辑:moviepy中的剪辑基类Clip详解>和<moviepy音视频剪辑:moviepy中的剪辑基类Cl ...

随机推荐

  1. commonhelper 通用类:计时器、数组去重、自动生成日志编号、生成随机数、处理字符串

    using System;using System.Collections.Generic;using System.Diagnostics;using System.Text; namespace ...

  2. Redmine it!

    redmine插件开发简介 最稳妥的学习应该是先看官方文档,官方还给了一个具体的插件开发教程,不过如果一步不差按照教程敲代码,其实会发现还是有些问题的,需要稍稍改动. 这里,我自己编写了一个简单的插件 ...

  3. async包 ES6 async/await的区别

    最基本的async 包 ApCollection.find({}).toArray(function (err, aps) { var num = 0; async.whilst( function ...

  4. Java - 面向对象练习 - market

    Marketpackage market; public class Market {    private String marname;    private Product[] producta ...

  5. Nginx笔记总结二:Nginx编译参数

    -prefix=                                                    安装路径-with-http_ssl_module               ...

  6. 吴裕雄--天生自然 R语言开发学习:回归

    #------------------------------------------------------------# # R in Action (2nd ed): Chapter 8 # # ...

  7. 基于OpenDDS应用程序开发(3)订阅端实现

    连续的三篇博文演示如何基于OpenDDS开发应用程序,将数据从发布端节点发送到订阅端节点,该示例程序由一个发布者发布数据,一个订阅者订阅数据,使用默认的QoS策略和TCP/IP传输方式. 本文是第三篇 ...

  8. iPhone X价格下跌!用户依旧冷眼相看为哪般?

    X价格下跌!用户依旧冷眼相看为哪般?" title="iPhone X价格下跌!用户依旧冷眼相看为哪般?"> 其实对于刚刚过去的2017年手机市场来说,根本没有一款 ...

  9. 批量修改datafram中某一列

    如要对df中列名为“values”的值做修改,大于等于50改为1,小于50改为0,可用apply函数来实现: def fun(x): if x >= 50: return 1 else: ret ...

  10. Python 字符编码判断

    题记 在获取中文字符的时候,如果出现乱码的情况,我们需要了解当前的字符串的编码形式.使用下面两种方法可以判断字符串的编码形式. 法一: isinstance(s, str) 用来判断是否为一般字符串 ...