类声明、类作用域、前向声明、this指针、嵌套类、PIMPL 技法 等
一、类声明
//类是一种用户自定义类型,声明形式:
class 类名称
{
public:
公有成员(外部接口)
private:
私有成员
protected:
保护成员
};
在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
在关键字protected后面声明,与private类似,其差别表现在继承与派生时对派生类的影响不同。
|
1
2 3 4 5 6 7 8 9 10 11 |
class Clock
{ public: void Display(); void Init(int hour, int minute, int second); private: |
假设定义了一个Clock 类,因为成员是private的,那么 Clock ck; ck.hour_ = 12; 是错误的,对此定义一个public 的void SetHour(int hour) 来设置hour_ 的值。
二、内联成员函数、成员函数的重载及其缺省参数
在这里有内联函数的概念。成员函数也可以是内联的,若在类内部实现,inline 关键字可加可不加;在类外部实现,需加inline,
如 inline void Clock::SetHour(int hour) { } 。实际上即使加了inline也不一定宏展开,比如遇到switch,for 语句的时候就往往不会。
此外,成员函数也像一般函数那样可以重载,也可以有缺省参数,参考这里。
三、类与结构体
class与struct的区别:在未指定访问权限时,class默认的是私有的,struct默认是公有的,
struct Test
{
int X;//公有的
...
};
此外,Test 可以独立作为一个tag,而不像C语言那样需要 struct Test 作为一个类型。
四、隐含的 this 指针
成员函数有一个隐含的附加形参,即指向该对象的指针,这个隐含的形参叫做this指针(编译器自动传递)
使用this指针保证了每个对象可以拥有不同数值的数据成员,但处理这些成员的代码可以被所有对象共享
成员函数是只读的代码,由所有对象共享,并不占对象的存储空间,因为this指针指向当前对象,所以成员函数可以区分它所作用的对象是哪一个。
(哪个对象调用了this所在的函数,this就代表哪个对象)
再来看一道经典的题目:
|
1
2 3 4 5 6 7 8 9 10 11 12 |
class A
{ public: int m; void print() { cout << "A" << endl; } }; A *pa = 0; |
可以理解为如下的C代码:
|
1
2 3 4 5 6 |
void print(A *this)
{ cout << "A" << endl; } A *pa = 0; print_A(pa); |
相当于成员函数传递的this指针为0,那调用会出错吗? 肯定是正确输出"A" 的,因为this为0 表示没有对某个对象进行操作,而print里面确实没有对某
个对象成员进行操作,所以是可以运行的。
五、类作用域、前向声明
(1)、每个类都定义了自己的作用域称为类作用域,类作用域中说明的标识符只在类中可见。除了类作用域,还有块作用域、文件作用域、函数原型作用域、函数作用域,举个例子:
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
#include <iostream>
using namespace std; class Test //num_ = 20; Error,num_的作用域在类内部 int add(int a, int b); // a, b两个标识符的作用域为函数原型作用域 int main(void) cout << num_ << endl; int add(int a, int b) // 形参a与b也算是块作用域 int test() |
(2)、C++中类必须先定义,才能够实例化。两个类需要相互引用头文件形成一个“环形”引用时会出错。这时候需要用到前向声明,前向声明的类不能实例,但可以定义指针或引用。
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#ifndef _B_H_
#define _B_H_ class A; class B void fun(A &a) } A *a_; // 前向声明的类不能实例化对象 #endif // _B_H_ |
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
#ifndef _A_H_
#define _A_H_ #include "B.h" B b_; #endif // _A_H_ |
六、嵌套类、局部类
(1)、嵌套类
外围类需要使用嵌套类对象作为底层实现,并且该嵌套类只用于外围类的实现,且同时可以对用户隐藏该底层实现。
从作用域的角度看,嵌套类被隐藏在外围类之中,该类名只能在外围类中使用。如果在外围类之外的作用域使用该类名时,需要加名字限定。
嵌套类中的成员函数可以在它的类体外定义。
嵌套类的成员函数对外围类的私有成员没有访问权,反之亦然。
嵌套类仅仅只是语法上的嵌入
(2)、局部类
类也可以定义在函数体内,这样的类被称为局部类(local class)。局部类只在定义它的局部域内可见。
局部类的成员函数必须被定义在类体中。
局部类中不能有静态成员,关于类中的静态成员和静态成员函数以后再谈。
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
#include <iostream>
using namespace std; class Outer void Outer::Inner::Fun() void Fun() //static int num2_; // 局部类内部不能定义静态成员 LocalClass lc; int main(void) Outer::Inner i; Fun(); |
七、PIMPL 技法
来看下面的示例:
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// file y.h
#include "x.h" class Y { void Fun(); X x_;
};
// file y.cpp #include "y.h" void Y::Fun { return x_.Fun(); } // file main.cpp #include “y.h” int main(void) { Y y; y.Fun(); } |
上面程序存在的问题是:
1、引入更多的头文件,降低编译速度
2、在编译期如果X的大小改变了,y.cpp 和 main.cpp 都得重新编译;在运行期,如果X有子类,也不能使用多态虚函数。
3、假设y.cpp 编译成动态库给main.cpp 使用,当X的大小变化,动态库需要重新编译,此时main.cpp 因为有定义对象y ,故也需要
重新编译。
下面介绍一种PIMPL 技法,有人也把它当作一种设计模式:
PIMPL(private implementation或pointer to implementation)也称为handle/body idiom
PIMPL背后的思想是把客户与所有关于类的私有部分的知识隔离开。避免其它类知道其内部结构
降低编译依赖、提高重编译速度
接口和实现分离
降低模块的耦合度
编译期
运行期
提高了接口的稳定程度
对于库的使用,方法不用改变
对于库的编译,动态库的变更,客户程序不用重新编译
修改后的程序:
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
// file y.h
class X; class Y { Y(); ~Y(); void Fun(); X *px_; }; // file y.cpp #include "x.h" Y::Y() : px_( new X ) {} Y::~Y() { delete px_; px_ = 0; } void Y::Fun() { return px_->Fun(); } // file main.cpp #include "y.h" int main(void) { Y y; y.Fun(); } |
即Y 内部成员是X* 指针,在32位系统上,指针大小固定为4个字节,即使X大小改变,也不影响Y。如果X 有子类,通过基类指针px_
还可以实现虚函数多态。
参考:
C++ primer 第四版
Effective C++ 3rd
C++编程规范
类声明、类作用域、前向声明、this指针、嵌套类、PIMPL 技法 等的更多相关文章
- C++ 类 & 对象-C++ 内联函数-C++ this 指针-C++ 类的静态成员
C++ 内联函数 C++ 内联函数是通常与类一起使用.如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方. 对内联函数进行任何修改,都需要重新编译函数的所有客户端 ...
- C++中类的前向声明
概念 可以声明一个类而不是定义它; class Screen; 这个声明被称为"前向声明".在声明之后,定义之前,类screen是一个不完全类型,即已知Screen是一个类型,但不 ...
- C++拾遗(十三)友元和嵌套类
友元类 使用友元的场合: 1.两个类既不是is-a关系也不是has-a关系,但是两个类之间又需要有联系,且一个类能访问另一个类的私有成员和保护成员. 2.一个类需要用到另外多个类的私有成员. C++p ...
- C++学习之嵌套类和局部类
C++学习之嵌套类和局部类 局部类 在一个函数体内定义的类称为局部类. 局部类中只能使用它的外围作用域中的对象和函数进行联系,因为外围作用域中的变量与该局部类的对象无关.在定义局部类时需要注意:局部类 ...
- 嵌套类,PIMPL
body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...
- final与 static的区别;static代码块以及嵌套类介绍
本篇文章主要分为两个模块进行介绍:1.final,staic,static final之间的异同:2. static 模块:3.嵌套类的概念 1.final,staic,static final之间的 ...
- C++ 嵌套类使用(三)
如果嵌套类型和其外部类型之间的关系需要成员可访问性语义,需要使用C++嵌套类,嵌套类型不应针对其声明类型以外的类型执行任务,而C++局部类允许类.结构和接口被分成多个小块儿并存储在不同的源文件中,这样 ...
- C++ 嵌套类使用(一)
一.嵌套类 在一个类的内部定义另一个类,我们称之为嵌套类(nested class),或者嵌套类型.之所以引入这样一个嵌套类,往往是因为外围类需要使用嵌套类对象作为底层实现,并且该嵌套类只用于外围类的 ...
- C++学习笔记(十二):类继承、虚函数、纯虚函数、抽象类和嵌套类
类继承 在C++类继承中,一个派生类可以从一个基类派生,也可以从多个基类派生. 从一个基类派生的继承称为单继承:从多个基类派生的继承称为多继承. //单继承的定义 class B:public A { ...
随机推荐
- malloc和calloc的差别
做C这么久了,才了解calloc函数也是挺丢人的. 从网上找了非常多关于这两者差别的文章.有的甚至总结了好多人的结论.但我感觉都没有说的非常明确. 当中关于函数原型的差别根本就不是必需再讨论了,是个人 ...
- Spring bean配置继承
在 Spring,继承是用为支持bean设置一个 bean 来分享共同的值,属性或配置. 一个子 bean 或继承的bean可以继承其父 bean 的配置,属性和一些属性.另外,子 Bean 允许覆盖 ...
- 一段js的思考
写了很久JS,还以为这段代码可以正常输出,谁知道输出超乎我的形象: <!DOCTYPE html> <html> <head> <meta charset=& ...
- java什么时候声明static方法
1.经常用到的方法,可以声明为static,这样省去了每次new对象的内存空间,因为非static方法,需要new对象才能调用此方法.但因此也产生多线程访问线程安全问题 比如: 2.当一个方法或者变量 ...
- shiro中部分SpringCache失效问题
原文:https://www.cnblogs.com/liruiloveparents/p/9392159.html shiro中部分SpringCache失效问题 1.问题抛出 今天在做Spri ...
- 【mybatis】mysql级联更新两个表或多张表的数据
例如 info表和relation表有关联,现在要在一个sql语句中同时级联更新两张表的数据 update security_code_info info LEFT JOIN security_cod ...
- cocos2d-x HelloWorld 代码一撇
本节简单对新生成的hellowrold 项目相关代码进行简单分析,具体以代码注释的方式展示给大家.代码相对简单些,在此不作过多赘述,直接上码: int APIENTRY _tWinMain(H ...
- python笔记19-获取当前运行函数名称和类方法名称
前言 写完代码之后,一般为了方便查看日志,可以在日志输出中加入当前运行的函数名称或类和方法名称,以便于代码报错的时候能快速找到报错的是哪个函数或方法. 那么如何获取当前运行函数(或方法)的名称呢? 获 ...
- redis的主从配置 扩容
由于redis的集群功能没有完全编写好,见:http://redis.io/topics/cluster-spec 集群只有测试版的第8个版本redis3.0.0 见 http://redis.io/ ...
- 仿LOL项目开发第三天
仿LOL项目开发第二天 by草帽 昨个我们已经实现了下载功能,但是发现没有,下载的包是压缩的,没有解压开,那么Unity是识别不了的. 所以今个我们来讲讲如何实现解压文件. 还记得吗,我们在Downl ...