目录
一、类的声明和定义
1.1 类声明
类就是定义一个新的类型和一个新作用域,类(class)与结构体(struct)、联合体(union)是用户定义类型,以类说明符定义,它在声明语法的声明说明符序列中出现。
/*类关键词 属性 类头名 基类子句 { 成员说明 }*/
//demo
class X {public: int val;};
class Y : public X {public: void setval();};
struct A { int id; };
struct B : public A { double val;};
union U{ int a; char b; };
/*
类关键词 - class 或 struct或union 之一。除了默认成员访问和默认基类访问之外,两个关键词是等同的。
属性 - (C++11 起) 可选的任任意数量属性序列,可以包含 alignas 指定符
类头名 - 所定义的类的名字。可以有限定,也可以后随关键词 final。名字可以省略,此时类是无名的(注意无名类不能是 final 的)
基类子句 - 一或多个基类以及各自所用的继承模型的可选列表
成员说明 - 访问说明符、成员对象及成员函数的声明和定义的列表
如果 类关键词 是 union,那么声明引入一个联合体类型。
*/
每个类可以没有成员,也可以定义多个成员,成员可以是数据、函数或类型别名。一个类可以包含若干公有的(public )、私有的(private)和受保护(protected)的部分。在 public 部分定义的成员可被使用该类型的所有代码访问;在 private 部分定义的成员可被类成员或友元访问。在protected定义的成员可以被派生类或友元访问。
所有成员必须在类的内部声明,一旦类定义完成后,就没有任何方式可以增加成员了。
1.2 类的主要特性
类背后蕴涵的基本思想是数据抽象和封装。类是一个封装的实体:它代表若干成员的聚焦,大多数(良好设计的)类类型隐藏了实现该类型的成员。类在使用方面它是抽象的,只需考虑它的接口,即它能执行的操作。它又是封装的,因为使用者既无法了解该类型如何表示的细节,也无法访问其任意的实现制品。
class Base
{
public:
int ival;
void func1(void);
protected:
float fval;
void func2(void);
private:
double dval;
void func3(void);
};
通常我们会把数据隐秘起来(private,成员变量或函数,成员函数用于内部使用),对外提供可访问接口(public 、protected,成员函数,有时会暴露成员变量)。如果数据是私有的并且没有改变成员函数的接口,则操纵类对象的用户函数无须改变。类设计者必须关注类是如何实现的,但使用该类的程序员仅需了解类型的接口能力,他们可以抽象地考虑该类型做什么,而不必具体地考虑该类型如何工作。
PS,并非所有类型都必须是抽象的,依据使用性,一些类具体类会暴露而非隐藏其实现细节。类设计者应秉着使用者需求的角度考量,设计良好的、实用的类。
类的另一个核心特性是面向对象编程的展现的多态性(polymorphism),即类类型通过继承而相关联的类型为多态类型,是因为在许多情况下可以互换地使用派生类型或基类型的“许多形态”。在 C++ 中,多态性仅用于通过继承而相关联的类型的引用或指针。通过继承我们能够定义这样的类,它们对类型之间的关系建模,共享公共的东西,仅仅特化本质上不同的东西。派生类(derived class)能够继承基类(baseclass)定义的成员,派生类可以无须改变而使用那些与派生类型具体特性不相关的操作,派生类可以重定义那些与派生类型相关的成员函数,将函数特化,考虑派生类型的特性。最后,除了从基类继承的成员之外,派生类还可以定义更多的成员。因继承而相关联的类为构成了一个继承层次。其中有一个类称为根,其他类直接或间接继承根类。
1.3 类的定义
类的定义时,遇到右花括号,就表示定义结束,注意类定义语句需要添加;结尾的。编译器由此就可以明确所有的类成员,以及存储该类的对象所需的存储空间。在一个给定的源文件中,一个类只能被定义一次。如果在多个文件中定义一个类,那么每个文件中的定义必须是完全相同的。通常会将类定义在头文件中,可以保证在每个使用类的文件中以同样的方式定义类,然后在源文件中定义成员函数、内置内、动态对象等的实现。将成员函数等在类定义体外定义,这是实现封装了另一种方式,例如我们仅仅提供头文件和静态或动态库给使用者,而非源码。
//a.h
#ifndef _A_H_
#define _A_H_
class A {
public:
void setval(int val_);
private:
int val;
};
#endif //_A_H_
//a.cpp
void A::setval(int val_)
{
//code
};
每个类都定义了自己的新作用域和唯一的类型。在类的定义体内声明类成员,将成员名引入类的作用域。两个不同的类具有两个的类作用域。即使两个类具有完全相同的成员列表,它们也是不同的类型。每个类的成员不同于任何其他类(或任何其他作用域)的成员。
class A { private: int x; double y; };
class B { private: int x; double y; };
//
A a;
B b = a; //error
尽管成员函数等大多在类的定义体之外定义的,但其定义就好像它们是在类的作用域中一样,和成员函数在类定义体内定义的区别就是,定义体内定义其默认为函数内联性质,在定义体外定义,需要显式指定inline才是内联性质。
class X
{
public:
void func1(){ val=1;}; //inline
void func2();
void func3();
private:
int val;
};
//
void X::func2() { val=2;};//普通成员函数
void X::func3() { val=3;};//inline成员函数
二、类的组合关系
2.1 前置声明引用
通常,可以声明一个类而不定义它:
class ATest;
这个声明,有时称为前向声明(forward declaraton)或前置声明,在程序中引入了类类型的 ATest。在声明之后、定义之前,类ATest是一个不完全类型(incompete type),即已知ATest是一个类型,但不知道包含哪些成员。不完全类型(incomplete type)只能以有限方式使用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。
//test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_
class ATest;
class CTest{};
class BTest
{
public:
BTest();
~BTest();
void doit();
// ATest a; //error,这是一个不完全类型
ATest *pa; //OK,不完全类型能用于定义指向该类型的指针
CTest c; //OK
};
#endif //_TEST_1_H_
//test1.cpp
class ATest
{
public:
int val;
};
BTest::BTest(){
pa = new ATest;
};
BTest::~BTest(){
delete pa; pa=nullptr;
}
void BTest::doit()
{
// ++a.val; //error
++(pa->val);//OK
};
通过前置声明,可以将一些类类型定义延迟至源文件内实现,使得更好地隐藏实现细节,也可以将类使用者不关心的一些类类型屏蔽掉,给使用者更清晰明了的应用接口。
标准库里也包含有前置声明支持的头文件<*fwd>,而且如果特定的源文件只用到该类的指针和引用,使用前置声明也可以减少 #include 依赖:
#include <iosfwd> // 含有 std::ostream 的前置声明
class PTest
{
private:
/* data */
std::string *pstr; //<iosfwd>中#include <bits/stringfwd.h>
// std::string str; //error
public:
friend std::ostream& operator<<(std::ostream& os, const PTest& s);//<iosfwd> // 含有 std::ostream 的前置声明
};
因为只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员,当类类型使用自身相当于使用一个前置声明的类,即类的数据成员可以是指向自身类型的指针或引用:
class DTest
{
private:
/* data */
public:
DTest *prt;
};
如果前置声明在局部作用域出现,那么它会隐藏它的外围作用域中可能出现的先前声明的相同名字的类、变量、函数,以及所有其他声明:
//示例1
struct s { int a; };
struct s; // 不做任何事(s 已经在此作用域定义)
void g()
{
struct s; // 新的局部结构体“s”的前置声明,它隐藏全局的结构体 s 直至此块结尾
s* p; // 指向局部结构体 s 的指针
struct s { char* p; }; // 局部结构体 s 的定义
*(p->p) = 'a';//OK,使用局部声明
//p->a = 1; //error
};
//或示例2
struct ts{
struct s; // 新的局部结构体“s”的前置声明,它隐藏全局的结构体 s 直至此块结尾
s* p; // 指向局部结构体 s 的指针
struct s { char* p; }; // 局部结构体 s 的定义
void doit()
{
*(p->p) = 'a'; //OK
// p->a = 1; //error
};
};
前置声明,通过作为其他声明一部分的详述类型说明符也可以引入新的类名,但只有在名字查找无法找到先前声明的有此名的类时才可以。
#ifndef _TEST_2_H_
#define _TEST_2_H_
class K;
namespace ns{
class Y f1(class T p); // 声明函数 ns::f1 并前置声明类 ns::T 与 ns::Y
class K f2(); // K 指代 ::K,和class Y是不同的
};
void nstest(void);
#endif //_TEST_2_H_
//test2.cpp
#include "test2.h"
class K
{
private:
/* data */
int val;
public:
};
class ns::Y{};
class ns::T{};
ns::Y ns::f1(ns::T p)
{
return ns::Y();
};
::K ns::f2()
{
return ::K();
};
void nstest(void)
{
ns::T p;
ns::Y y = ns::f1(p);
::K k = ns::f2();
};
2.2 友元关系
在某些情况下,允许特定的非成员函数或类访问一个类的私有成员,同时仍然阻止一般的访问,这是很方便做到的,就是将非成员函数或类声明给该类的友元。
例如,被重载的操作符,如输入或输出操作符,经常需要访问类的私有数据成员。这些操作符不可能为类的成员。然而,尽管不是类的成员,它们仍是类的“接口的组成部分”。
#include <ostream>
class Empty{};
class PClass
{
private:
int val;
void g(){val = 10; };
public:
void func(){ val = 100; };
// protected:
// private: //可以public protected private
friend class FClass;//友元类前置声明
friend Empty; //友元类声明(简单类型说明符) (C++11 起)
friend std::ostream& operator<<(std::ostream &os,const PClass &obj);
};
class FClass
{
private:
/* data */
public:
PClass pc;
void func()
{
pc.val = 22; 直接使用私有成员
pc.g(); 直接使用私有成员
pc.func(); //
};
};
//test2.cpp
#include "test2.h"
std::ostream& operator<<(std::ostream &os,const PClass &obj)
{
os << obj.val; //直接使用私有成员
return os;
}
友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。友元的声明以关键字 friend 开始。它只能出现在类定义的内部。友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响。通常,将友元声明成组地放在类定义的开始或结尾处。
友元可以是普通的非成员函数,或前面定义的其他类的成员函数,或整个类。上述例子中,不必预先声明类和非成员函数来将它们设为友元。友元声明将已命名的类或非成员函数引入到外围作用域中。
将一个类设为友元,友元类的所有成员函数都可以访问授予友元关系的那个类的非公有成员,这样就会弱化类的封装性。通常建议针对一个具体成员函数给与友元,既能尽量保持封装性,又能满足使用便利性。将成员函数声明为友元时,函数名用该函数所属的类名字加以限定即可。
#include <ostream>
class FClass
{
private:
/* data */
public:
void func();
};
class PClass
{
private:
int val;
void g(){val = 10; };
public:
void func(){ val = 100; };
// protected:
// private:
// friend class FClass; //
friend void FClass::func(); //直接面向成员函数
friend std::ostream& operator<<(std::ostream &os,const PClass &obj);
};
//test2.cpp
#include "test2.h"
std::ostream& operator<<(std::ostream &os,const PClass &obj)
{
os << obj.val; //直接使用私有成员
return os;
}
void FClass::func()
{
PClass pc;
pc.val = 22; 直接使用私有成员
pc.g(); 直接使用私有成员
pc.func(); //
};
上述代码可以看到,先定义包含成员函数的类,才能将成员函数设为友元。
可以定义一个非成员函数,同时令其为此类的友元。这种非成员函数始终是内联函数,除非它附着到一个具名模块 (C++20 起)。
class PClass
{
private:
int val;
public:
friend void func1(PClass& obj) { obj.val = 100; };//内联友元,非成员函数
};
//main.cpp
PClass pc;
func1(pc);//直接使用内部私有成员
对于声明为友元后,对于嵌套类型,同样也能访问:
class PClass
{
private:
enum { a = 10 }; // 私有枚举
class DeriveA { }; // 私有嵌套类型
public:
friend class FDerive;
};
class FDerive : PClass::DeriveA // OK:友元能访问 PClass::DeriveA
{
PClass::DeriveA mx; // OK:友元的成员能访问 PClass::DeriveA
class DeriveB {
PClass::DeriveA my; // OK:友元的嵌套成员能访问 PClass::DeriveA
};
int v[PClass::a]; // OK:友元的成员能访问其他私有成员 PClass::a
};
友元的使用需要注意一下约束要求:
- 友元关系不传递(你朋友的朋友不是你的朋友)。
- 友元关系不继承(你朋友的孩子不是你的朋友)。
- 友元函数声明中不允许使用存储类说明符。在友元声明中定义的函数具有外部链接,先前已定义的函数保持其定义时所具有的链接。
- 访问说明符对于友元声明的含义没有影响(它们可以在 private: 或 public: 区间出现,且没有区别)。
- 友元类声明不能定义新的类(friend class X {}; 是错的)。
class TF1
{
private:
/* data */
int val;
public:
friend class TF2;
// friend class NewTF{}; //error,不能像普通函数一样在类内定义主体
};
class TF2
{
public:
void func(){
TF1 tf1;
tf1.val = 10; //OK
}
friend class TF4;
};
class TF3 : public TF2
{
private:
/* data */
public:
void func(){
TF1 tf1;
// tf1.val = 10; //error,友元不能继承
}
};
class TF4
{
public:
void func(){
TF1 tf1;
// tf1.val = 10; //error,友元不能传递
}
};
2.3 类继承或派生
类类型可以作为基类被其他类型继承,继承类为该类的派生类。基类定义其接口和实现的数据和函数成员,通过继承保护方式传递给派生类,派生类延续了基类的特性,并在此基础上拓展自己的特性。
class Base
{
public:
int ival;
};
class Child : public Base
{
public:
char cval;
};
类继承有public、protected、private三种受限继承方式,从而决定派生类对基类成员的访问控制。
#ifndef _TEST_3_H_
#define _TEST_3_H_
class Base
{
public:
int ival;
protected:
float fval;
private:
double dval;
};
class Child1 : public Base
{
public:
char cval;
void doit_public();
};
class Child2 : protected Base
{
public:
char cval;
void doit_public();
};
class Child3 : private Base
{
public:
char cval;
void doit_public();
};
void test_derive(void);
#endif //_TEST_3_H_
#include "test3.h"
void Child1::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
}
void Child2::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
}
void Child3::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
}
void test_derive(void)
{
Child1 c1;
c1.ival = 1;
// c1.fval = 1.0; //error
c1.doit_public();
Child2 c2;
// c2.ival = 1; //error
// c2.fval = 1.0; //error
c2.doit_public();
Child3 c3;
// c3.ival = 1; //error
// c3.fval = 1.0; //error
c3.doit_public();
};
如果没有继承,类只有两种用户:类本身的成员和该类的用户。将类划分为 private 和 public 访问级别反映了用户种类的这一分隔:用户只能访问 public 接口,类成员和友元既可以访问 public 成员也可以访问 private 成员。
有了继承,就有了类的第三种用户:从类派生定义新类的程序员。派生类的提供者通常(但并不总是)需要访问(一般为 private 的)基类实现,为了允许这种访问而仍然禁止对实现的一般访问,提供了附加的protected 访问标号。类的 protected 部分仍然不能被一般程序访问,但可以被派生类访问。只有类本身和友元可以访问基类的 private 部分,派生类不能访问基类的 private 成员。
定义类充当基类时,将成员设计为 public 的标准并没有改变:仍然是接口函数应该为 public 而数据一般不应为 public。被继承的类必须决定实现的哪些部分声明为 protected 而哪些部分声明为 private。希望禁止派生类访问的成员应该设为 private,提供派生类实现所需操作或数据的成员应设为 protected。而通过派生类去访问基类成员的必须是public继承才有权限。
类派生列表可以指定多个基类。继承单个基类是为常见,但是有时也需要支持多重继承,即是从多于一个直接基类派生类的能力,多重继承的派生类继承其所有父类的属性。类可以是直接继承多个基类的派生类,可以分别制定各个基类的继承受限约束(public、protected、private),多基类集成时,需要注意成员名冲突问题,在应用时,成员名冲突会引发歧义,编译器无法决议而无法通过编译。
class Base1
{
public:
int ival;
int ival1;
protected:
float fval;
private:
double dval;
};
class Base2
{
public:
int ival;
int ival2;
protected:
float fval2;
private:
double dval2;
};
class Childn : public Base1, protected Base2
{
public:
void doit_public();
};
//test3.cpp
void Childn::doit_public()
{
// ival = 1; //继承冲突
ival2 = 1; //
fval = 1.0;
fval2 = 1.0;
// dval = 1.0; //error
// dval2 = 1.0; //error
};
void test_derive(void)
{
Childn cn;
// cn.ival = 1; //error,变量名冲突
cn.ival1 = 1; //OK
// cn.ival2 = 1; //error,protected继承
cn.doit_public();
};
另外派生类也会作为其他类的基类继续被继承。
class Base1
{
public:
int ival;
int ival1;
protected:
float fval;
private:
double dval;
};
class Child1 : public Base1
{
public:
char cval;
void doit_public();
};
class CChild1 : public Child1
{
public:
void doit_public();
};
//test3.cpp
#include "test3.h"
void Child1::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
}
void CChild1::doit_public()
{
ival = 1; //Base1的成员
fval = 1.0; //Base1的成员
cval = 'a'; //Child1的成员
// dval = 1.0; //error
}
void test_derive(void)
{
CChild1 cc1;
cc1.ival = 1; //Base1的成员
cc1.cval = 'b'; //Child1的成员
cc1.doit_public();
};
构造函数初始化式只能控制用于初始化基类的值,不能控制基类的构造次序。基类构造函数按照基类构造函数在类派生列表中的出现次序调用。对 CChild1而言,基类初始化的次序是:
- Base1,从 CChild1的直接基类 Base1沿层次向上的最终基类。
- Child1,直接基类。
- CChild1,初始化 CChild1本身的成员,然后运行它的构造函数的函数体。
析构的次序,总是按构造函数运行的逆序调用析构函数。在例子中,调用析构函数的次序是 ~CChild1, ~Child1, ~Base1。
用基类的指针或引用只能访问基类中定义(或继承)的成员,不能访问派生类中引入的成员。当一个类继承于多个基类的时候,那些基类之间没有隐含的关系,不允许使用一个基类的指针访问其他基类的成员。
#ifndef _TEST_3_H_
#define _TEST_3_H_
class Base1
{
public:
int ival;
int ival1;
protected:
float fval;
private:
double dval;
};
class Base2
{
public:
int ival;
int ival2;
protected:
float fval2;
private:
double dval2;
};
class Child1 : public Base1
{
public:
char cval;
void doit_public();
};
class CChild1 : public Child1
{
public:
void doit_public();
};
class CChild2 : public Child1 , public Base2
{
public:
void doit_public();
};
void test_derive(void);
#endif //_TEST_3_H_
//test3.cpp
#include "test3.h"
#include <iostream>
void Child1::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child1::doit_public\n";
}
void CChild1::doit_public()
{
ival = 1;
fval = 1.0;
cval = 'a';
// dval = 1.0; //error
std::cout << "CChild1::doit_public\n";
}
void CChild2::doit_public()
{
// ival = 1; //error,冲突
ival1 = 1; //Base1
ival2 = 1; //Base2
fval = 1.0; //Base1
fval2 = 1.0; //Base2
cval = 'a';
// dval = 1.0; //error
std::cout << "CChild2::doit_public\n";
}
void dfunc(Child1 &c)
{
c.doit_public();
};
void test_derive(void)
{
CChild1 cc1;
cc1.ival = 1;
cc1.cval = 'b';
cc1.doit_public(); //CChild1::doit_public
Child1 *pc1 = &cc1;
pc1->doit_public(); //Child1::doit_public
dfunc(c1); //Child1::doit_public
dfunc(cc1); //Child1::doit_public
CChild2 cc2;
cc2.doit_public(); //CChild2::doit_public
pc1 = &cc2;
pc1->doit_public(); //Child1::doit_public
};
在多重继承下,一个基类可以在派生层次中出现多次,多重继承的类从它的每个父类继承状态和动作。,如果某个类型使用常规继承了两个父类,而这两个父类有共同继承了其他Base类,该类型则每个对实例象可能包含两个 Base子对象这从设计角度讲,这个实现正是错误。
在 C++ 中,通过使用虚继承解决这类问题。虚继承是一种机制,类通过虚继承指出它希望共享其虚基类的状态。在虚继承下,对给定虚基类,无论该类在派生层次中作为虚基类出现多少次,只继承一个共享的基类子对象。共享的基类子对象称为虚基类。
#ifndef _TEST_3_H_
#define _TEST_3_H_
class Base1
{
public:
int ival;
int ival1;
protected:
float fval;
private:
double dval;
};
class Child1 : virtual public Base1 //虚继承,确保Base1仅一个副本存在
{
public:
char cval;
void doit_public();
};
class Child2 : protected virtual Base1 //虚继承,确保Base1仅一个副本存在
{
public:
char cval;
void doit_public();
};
class Child12 : public Child1 , public Child2 //虚继承,确保Base1仅一个副本存在
{
public:
void doit_public();
};
void test_derive(void);
#endif //_TEST_3_H_
//test3.cpp
#include "test3.h"
#include <iostream>
void Child1::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child1::doit_public\n";
}
void Child2::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child2::doit_public\n";
}
void Child12::doit_public()
{
ival = 1; //OK,虚继承,冲突消除
fval = 1.0; //OK,虚继承,冲突消除
// dval = 1.0; //error
std::cout << "Child12::doit_public\n";
}
void test_derive(void)
{
//
Child12 c12;
c12.doit_public(); //Child12::doit_public
Child1 *pc1 = &c12;
pc1->doit_public(); //Child1::doit_public
Child2 *pc2 = &c12;
pc2->doit_public(); //Child2::doit_public
};
2.4 嵌套类
可以在另一个类内部定义一个类,即class/struct 或 union 的声明可以在另一个类中出现,这样的类是嵌套类,也称为嵌套类型。嵌套类是独立的类,基本上与它们的外围类不相关,因此,外围类和嵌套类的对象是互相独立的。嵌套类型的对象不具备外围类所定义的成员,同样,外围类的成员也不具备嵌套类所定义的成员。
嵌套类的名字在其外围类的作用域中可见,但在其他类作用域或定义外围类的作用域中不可见。嵌套类的名字将不会与另一作用域中声明的名字冲突。嵌套类可以直接引用外围类的静态成员、类型名和枚举成员,当然,引用外围类作用域之外的类型名或静态成员,需要作用域确定操作符。
//
int x = 0; int y = 0;
class Test1
{
private:
/* data */
int val;
int x;
static int si;
enum eu{ a=1,b=2};
public:
int pival;
class Test2
{
private:
/* data */
int val;
// Test1 t1; //error
Test1 *pt1; //OK
public:
class Test3
{
private:
/* data */
int val;
public:
int pival;
};
Test3 t3;
// Test4 t4; //error;
class Test4 *pt4; //OK
void func(int val_)
{
val = val_; //local Test2变量
// x = val_; //error,外围类成员变量和全局变量同名
::x = val_; //OK,显式全局变量
y = val_; //OK,隐式匹配到全局变量
si = val_; //OK,静态成员
val = eu::a; //OK
// pival = val_;//error,外围类的public成员
t3.pival = val_; //OK
}
// void g1(Test4 *pt)
// {
// pt->pival = val; // error,Test4为不完整类
// };
class Test4
{
private:
/* data */
int val;
public:
int pival;
};
//
void g2(Test4 *pt)
{
pt->pival = val; // OK
};
};
void foo()
{
Test2 t2;
t2.func(10);
};
};
int Test1::si = 10;
void nestfunc(void)
{
Test1 t1;
t1.foo();
};
嵌套类的名字在它的外围类作用域中存在,而且从嵌套类的成员函数中进行名字查找会在检测嵌套类的作用域后访问外围类的作用域。 嵌套类可以具有与非嵌套类相同种类的成员,像任何其他类一样,嵌套类使用访问标号控制对自己成员的访问。成员可以声明为 public、private 或protected。外围类对嵌套类的成员没有特殊访问权,并且嵌套类对其外围类的成员也没有特殊访问权。
#include <iostream>
class Test1
{
private:
/* data */
int val;
public:
int pival;
class Test2
{
private:
/* data */
int val;
// Test1 t1; //error
Test1 *pt1; //OK,定义外围类指针
public:
Test2(){pt1 = new Test1();};
~Test2(){delete pt1; pt1 = nullptr;};
class Test3
{
private:
/* data */
int val;
public:
int pival;
};
Test3 t3; //定义内嵌类对象
void func(int val_)
{
t3.pival = val_; //OK
//确保指针有效
pt1->pival = val_; //OK
pt1->val = val_; //OK,直接访问了私有成员,宽松编译器
std::cout << "pt1->val = " << pt1->val << "\n";
}
};
};
嵌套类定义其外围类中的一个类型成员。像任何其他成员一样,外围类决定对这个类型的访问。在外围类的 public 部分定义的嵌套类定义可在任何地方使用的类型,在外围类的 protected 部分定义的嵌套类定义只能由外围类、友元或派生类访问的类型,在外围类的 private 部分定义的嵌套类定义只能被外围类或其友元访问的类型。
class Test4
{
private:
/* data */
int val;
class Test5
{
public:
void func(int val_){;};
friend Test4;
private:
void func1(int val_){;};
};
public:
Test5 pt5; //OK
class Test6
{
public:
void func(int val_){;};
friend void nestfunc2(void);
private:
void func1(int val_){;};
};
Test5 pt6; //OK
};
void nestfunc1(void)
{
Test4 t4;
t4.pt5.func(10); //OK
// t4.pt5.func1(10); //error
t4.pt6.func(10); //OK
// t4.pt6.func1(10); //error
};
void nestfunc2(void)
{
// Test4::Test5 t5;//error
Test4::Test6 t6;
t6.func1(10); //OK,友元函数调用私有成员
}
嵌套类通常支持外围类的实现细节。我们可能希望防止外围类的用户看见嵌套类的实现代码。
可以在外围类的定义体中声明然后定义嵌套类。例如,我们可能希望将 Test8类的定义放在它自己的文件中,我们可以在 Test7类及其成员的实现文件中包含这个文件。
class Test7
{
private:
struct Test8;
Test8 *pt8;
};
//*.cpp
struct Test7::Test8//为了在外围类的外部定义类体,必须用外围类的名字限定嵌套类的名字
{
int val;
};
当嵌套类与外围类同名时,在内部使用优先使用嵌套,在外围使用时,需要显式指定来确认。
struct Test9;
class Test7
{
private:
struct Test9; //采用作用域内内嵌的
Test9 *pt9;
public:
struct Test10;
};
struct Test9 //外围的
{
int val;
};
struct Test7::Test9//内嵌的
{
double val;
};
struct Test7::Test10
{
char val;
};
void nestfunc3(void)
{
::Test9 t9; //使用外围的
Test7::Test10 t10; //使用内嵌的
}
2.5 局部类
可以在函数体内部定义类,这样的类称为局部类。一个局部类定义了一个类型,该类型只在定义它的局部作用域中可见。局部类的成员是严格受限的,局部类只能访问在外围作用域中定义的类型名、static 变量和枚举成员,不能使用定义该类的函数中的变量。
#include <vector>
#include <algorithm>
#include <iostream>
void localFunc(void)
{
std::vector<int> v{1,2,3};
struct Local { //局部类
bool operator()(int n, int m) {
return n > m;
}
};
std::sort(v.begin(), v.end(), Local()); // C++11 起
for(int n: v) std::cout << n << ' ';
};
类声明在函数体内出现,此时它定义局部类。这种类的名字只存在于函数作用域中,且无法在函数外访问。
- 局部类不能拥有静态数据成员
- 局部类的成员函数无链接
- 局部类的成员函数必须完全在类体内定义
- 除闭包类型以外的 (C++14 起)局部类不能拥有成员模板
- 局部类不能拥有友元模板
- 局部类不能在类定义内定义友元函数
- 函数(包括成员函数)内的局部类的外围函数能访问的名字也可以被该局部类访问。
- 局部类不能用作模板实参 (C++11 前)
- 局部类不能使用函数作用域中的变量
int a=1, val=2;
void foo(int val)
{
static int si = 10;
enum Loc { a = 1024, b };
// Bar bar_f; //error,未定义
// Bar is local to foo
class Bar {
public:
// static int lsi; //error
Loc locVal; // ok: uses local type name
int barVal;
void call_test() //OK
{
fooBar(locVal);
};
// void fooBar(Loc l = a); // ok: default argument is
void fooBar(Loc l = a){};//OK
// template <typename T> void template_func(T t){}; //error
// friend void frtest(int a); //error
void doit()
{
// barVal = val; // error: val is local to foo
barVal = ::val; // ok: uses global object
barVal = si; // ok: uses static local object
locVal = b; // ok: uses enumerator
}
private:
int pval; //通常是不必要的
};
// Bar::fooBar(Loc l){}; //error
Bar bar;
int tval = bar.locVal; // OK
// ...
};
局部类定义体中的名字查找方式与其他类的相同。局部类成员声明中所用的名字必须在名字使用之前出现在作用域中,成员定义中所用的名字可以出现在局部类作用域的任何地方。没有确定为类成员的名字首先在外围局部作用域中进行查找,然后在包围函数本身的作用域中查找。
外围函数对局部类的私有成员没有特殊访问权,实际上,由于是在函数体内定义,局部类封装在局部作用域中,局部类中 private 成员几乎是不必要的,通常局部类的所有成员都为 public 成员。
可以将一个类嵌套在局部类内部。这种情况下,嵌套类定义可以出现在局部类定义体之外,但是,嵌套类必须在定义局部类的同一作用域中定义。照常,嵌套类的名字必须用外围类的名字进行限定,并且嵌套类的声明必须出现在局部类的定义中:
void foo2()
{
class Bar {
public:
// ...
class Nested; // declares class Nested
};
// definition of Nested
class Bar::Nested {
// ...
};
}
嵌套在局部类中的类本身是一个带有所有附加限制的局部类。嵌套类的所有成员必须在嵌套类本身定义体内部定义。
三、源码补充
编译指令g++ main.cpp test*.cpp -o test.exe -std=c++11
main.cpp
#include "test1.h"
#include "test2.h"
#include "test3.h"
class X {public: int val;};
class Y : public X {public: void setval();};
struct A { int id; };
struct B : public A { double val;};
union U{ int a; char b; };
struct s { int a; };
struct s; // 不做任何事(s 已经在此作用域定义)
void g()
{
struct s; // 新的局部结构体“s”的前置声明
// 它隐藏全局的结构体 s 直至此块结尾
s* p; // 指向局部结构体 s 的指针
struct s { char* p; }; // 局部结构体 s 的定义
*(p->p) = 'a';
//p->a = 1; //error
// void doit()
// {
// *(p->p) = 'a';
// }
};
struct ts{
struct s; // 新的局部结构体“s”的前置声明,它隐藏全局的结构体 s 直至此块结尾
s* p; // 指向局部结构体 s 的指针
struct s { char* p; }; // 局部结构体 s 的定义
void doit()
{
*(p->p) = 'a'; //OK
// p->a = 1; //error
};
};
int main(int argc, char* argv[])
{
nestfunc();
nstest();
localFunc();
test_derive();
PClass pc;
func1(pc);
return 0;
}
test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_
class ATest;
class CTest{};
class BTest
{
public:
BTest();
~BTest();
void doit();
// ATest a; //error
ATest *pa; //OK
CTest c; //OK
};
#include <iosfwd> // 含有 std::ostream 的前置声明
class PTest
{
private:
/* data */
std::string *pstr;
// std::string str; //error
public:
friend std::ostream& operator<<(std::ostream& os, const PTest& s);
};
class DTest
{
private:
/* data */
public:
DTest *prt;
};
#endif //
test1.cpp
#include "test1.h"
class ATest
{
public:
int val;
};
BTest::BTest(){
pa = new ATest;
};
BTest::~BTest(){
delete pa; pa=nullptr;
}
void BTest::doit()
{
// ++a.val; //error
++(pa->val);//OK
};
test2.h
#ifndef _TEST_2_H_
#define _TEST_2_H_
#include <ostream>
class FClass
{
private:
/* data */
public:
void func();
};
class PClass
{
private:
int val;
void g(){val = 10; };
enum { a = 10 }; // 私有枚举项
class DeriveA { }; // 私有嵌套类型
public:
void func(){ val = 100; };
// protected:
// private:
friend class FClass2;
friend void FClass::func();
friend std::ostream& operator<<(std::ostream &os,const PClass &obj);
friend void func1(PClass& obj) { obj.val = 100; };
friend class FDerive;
};
class FClass2
{
public:
};
class FDerive : PClass::DeriveA // OK:友元能访问 PClass::DeriveA
{
PClass::DeriveA mx; // OK:友元的成员能访问 PClass::DeriveA
class DeriveB {
PClass::DeriveA my; // OK:友元的嵌套成员能访问 PClass::DeriveA
};
int v[PClass::a]; // OK:友元的成员能访问 PClass::a
};
class TF1
{
private:
/* data */
int val;
public:
friend class TF2;
// friend class NewTF{}; //error,不能像普通函数一样在类内定义主体
};
class TF2
{
public:
void func(){
TF1 tf1;
tf1.val = 10; //OK
}
friend class TF4;
};
class TF3 : public TF2
{
private:
/* data */
public:
void func(){
TF1 tf1;
// tf1.val = 10; //error,友元不能继承
}
};
class TF4
{
public:
void func(){
TF1 tf1;
// tf1.val = 10; //error,友元不能传递
}
};
class K;
namespace ns{
class Y f1(class T p); // 声明函数 ns::f1 并前置声明类 ns::T 与 ns::Y
class K f2(); // K 指代 ::K
};
void nestfunc(void);
void nstest(void);
void localFunc(void);
void foo(int val);
#endif //_TEST_2_H_
test2.cpp
#include "test2.h"
std::ostream& operator<<(std::ostream &os,const PClass &obj)
{
os << obj.val; //直接使用私有成员
return os;
}
void FClass::func()
{
PClass pc;
pc.val = 22; 直接使用私有成员
pc.g(); 直接使用私有成员
pc.func(); //
};
class K
{
private:
/* data */
int val;
public:
};
class ns::Y{};
class ns::T{};
ns::Y ns::f1(ns::T p)
{
return ns::Y();
};
::K ns::f2()
{
return ::K();
};
void nstest(void)
{
ns::T p;
ns::Y y = ns::f1(p);
::K k = ns::f2();
};
//
#include <iostream>
int x = 0; int y = 0;
class Test1
{
private:
/* data */
int val;
int x;
static int si;
enum eu{ a=1,b=2};
public:
int pival;
class Test2
{
private:
/* data */
int val;
// Test1 t1; //error
Test1 *pt1; //OK
public:
Test2(){pt1 = new Test1();};
~Test2(){delete pt1; pt1 = nullptr;};
class Test3
{
private:
/* data */
int val;
public:
int pival;
};
Test3 t3;
// Test4 t4; //error;
class Test4 *pt4; //OK
void func(int val_)
{
val = val_; //local Test2变量
// x = val_; //error,外围类成员变量和全局变量同名
::x = val_; //OK,显式全局变量
y = val_; //OK,隐式匹配到全局变量
si = val_; //OK,静态成员
val = eu::a; //OK
// pival = val_;//error,外围类的public成员
t3.pival = val_; //OK
//确保指针有效
pt1->pival = val_; //OK
pt1->val = val_; //OK
std::cout << "pt1->val = " << pt1->val << "\n";
}
// void g1(Test4 *pt)
// {
// pt->pival = val; // error,Test4为不完整类
// };
class Test4
{
private:
/* data */
int val;
public:
int pival;
};
//
void g2(Test4 *pt)
{
pt->pival = val; // OK
};
};
void foo()
{
Test2 t2;
t2.func(10);
};
};
int Test1::si = 10;
void nestfunc(void)
{
Test1 t1;
t1.foo();
};
class Test4
{
private:
/* data */
int val;
class Test5
{
public:
void func(int val_){;};
friend Test4;
private:
void func1(int val_){;};
};
public:
Test5 pt5; //OK
class Test6
{
public:
void func(int val_){;};
friend void nestfunc2(void);
private:
void func1(int val_){;};
};
Test5 pt6; //OK
};
void nestfunc1(void)
{
Test4 t4;
t4.pt5.func(10); //OK
// t4.pt5.func1(10); //error
t4.pt6.func(10); //OK
// t4.pt6.func1(10); //error
};
void nestfunc2(void)
{
// Test4::Test5 t5;//error
Test4::Test6 t6;
t6.func1(10); //OK
}
struct Test9;
class Test7
{
private:
struct Test8;
struct Test9; //采用局部的
Test8 *pt8;
Test9 *pt9;
public:
struct Test10;
};
struct Test7::Test8
{
int val;
};
struct Test9
{
int val;
};
struct Test7::Test9
{
double val;
};
struct Test7::Test10
{
char val;
};
void nestfunc3(void)
{
::Test9 t9;
Test7::Test10 t10;
}
//
#include <vector>
#include <algorithm>
#include <iostream>
void localFunc(void)
{
std::vector<int> v{1,2,3};
struct Local {
bool operator()(int n, int m) {
return n > m;
}
};
std::sort(v.begin(), v.end(), Local()); // C++11 起
for(int n: v) std::cout << n << ' ';
};
int a=1, val=2;
void foo(int val)
{
static int si = 10;
enum Loc { a = 1024, b };
// Bar bar_f; //error,未定义
// Bar is local to foo
class Bar {
public:
// static int lsi; //error
Loc locVal; // ok: uses local type name
int barVal;
// void fooBar(Loc l = a); // ok: default argument is
void call_test() //OK
{
fooBar(locVal);
};
void fooBar(Loc l = a){};//OK
// template <typename T> void template_func(T t){}; //error
// friend void frtest(int a); //error
void doit()
{
// barVal = val; // error: val is local to foo
barVal = ::val; // ok: uses global object
barVal = si; // ok: uses static local object
locVal = b; // ok: uses enumerator
}
private:
int pval; //通常是不必要的
};
// Bar::fooBar(Loc l){}; //error
Bar bar;
int tval = bar.locVal; // OK
// ...
};
void foo2()
{
class Bar {
public:
// ...
class Nested; // declares class Nested
};
// definition of Nested
class Bar::Nested {
// ...
};
}
test3.h
#ifndef _TEST_3_H_
#define _TEST_3_H_
class Base1
{
public:
int ival;
int ival1;
protected:
float fval;
private:
double dval;
};
class Base2
{
public:
int ival;
int ival2;
protected:
float fval2;
private:
double dval2;
};
class Childn : public Base1, protected Base2
{
public:
void doit_public();
};
class Child1 : virtual public Base1
{
public:
char cval;
void doit_public();
};
class Child2 : protected virtual Base1
{
public:
char cval;
void doit_public();
};
class Child3 : private Base1
{
public:
char cval;
void doit_public();
};
class CChild1 : public Child1
{
public:
void doit_public();
};
class CChild2 : public Child1 , public Base2
{
public:
void doit_public();
};
class Child12 : public Child1 , public Child2
{
public:
void doit_public();
};
void test_derive(void);
#endif //_TEST_3_H_
test3.cpp
#include "test3.h"
#include <iostream>
void Childn::doit_public()
{
// ival = 1; //继承冲突
ival2 = 1; //
fval = 1.0;
fval2 = 1.0;
// dval = 1.0; //error
// dval2 = 1.0; //error
std::cout << "Childn::doit_public\n";
};
void Child1::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child1::doit_public\n";
}
void Child2::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child2::doit_public\n";
}
void Child3::doit_public()
{
ival = 1;
fval = 1.0;
// dval = 1.0; //error
std::cout << "Child3::doit_public\n";
}
void CChild1::doit_public()
{
ival = 1;
fval = 1.0;
cval = 'a';
// dval = 1.0; //error
std::cout << "CChild1::doit_public\n";
}
void CChild2::doit_public()
{
// ival = 1; //error,冲突
ival1 = 1; //Base1
ival2 = 1; //Base2
fval = 1.0; //Base1
fval2 = 1.0; //Base2
cval = 'a';
// dval = 1.0; //error
std::cout << "CChild2::doit_public\n";
}
void dfunc(Child1 &c)
{
c.doit_public();
};
void Child12::doit_public()
{
ival = 1; //OK,虚继承,冲突消除
fval = 1.0; //OK,虚继承,冲突消除
// dval = 1.0; //error
std::cout << "Child12::doit_public\n";
}
void test_derive(void)
{
Child1 c1;
c1.ival = 1;
// c1.fval = 1.0; //error
c1.doit_public();
Child2 c2;
// c2.ival = 1; //error
// c2.fval = 1.0; //error
c2.doit_public();
Child3 c3;
// c3.ival = 1; //error
// c3.fval = 1.0; //error
c3.doit_public();
Childn cn;
// cn.ival = 1; //error,变量名冲突
cn.ival1 = 1; //OK
// cn.ival2 = 1; //error,protected继承
cn.doit_public();
CChild1 cc1;
cc1.ival = 1;
cc1.cval = 'b';
cc1.doit_public(); //CChild1::doit_public
Child1 *pc1 = &cc1;
pc1->doit_public(); //Child1::doit_public
dfunc(c1); //Child1::doit_public
dfunc(cc1); //Child1::doit_public
CChild2 cc2;
cc2.doit_public(); //CChild2::doit_public
pc1 = &cc2;
pc1->doit_public(); //Child1::doit_public
//
Child12 c12;
c12.doit_public(); //Child12::doit_public
// Child1 *pc1 = &c12;
pc1 = &c12;
pc1->doit_public(); //Child1::doit_public
Child2 *pc2 = &c12;
pc2->doit_public(); //Child2::doit_public
};