C++const成员、static成员、成员函数指针、友元
#include <iostream>
using namespace std;
class Classconst
{
public:
const int i1; //const数据成员,c++要求const类型必须显式初始化,当构造函数执行到函数体{}时对象及其成员已经被声明了,所以const成员无法在函数体内初始化
Classconst(int val=5) //默认参数兼顾无参构造器
:i1(val),i2(i1) //const限定的数据成员一般通过成员初始化列表进行初始化,格式为: 构造函数():数据成员(值){函数体} ,初始化写在块前面、形参列表后面以冒号开头,(对于成员对象来说)效率比在块中赋值高(因为进入块之前需要默认初始化,进入块后再执行赋值运算符重载,而直接通过拷贝构造器省一次函数调用),使用这种方式时如果数据成员之间相互赋值(如成员c=成员b),则类中b的声明需要在c前面/上面才对c可见,这也表明成员的初始化顺序是按类中声明的顺序,在声明、初始化上一个成员时,下一个成员还不存在所以不可见,注意初始化列表中各项的先后顺序与初始化顺序无关
{}
void dis() //普通函数成员
{
i2 = 3;
cout << i1 << endl;
}
void dis() const //const函数成员,格式为:函数头 const {函数体} ,声明const函数成员保证函数内不修改数据成员,包括函数的子块、调用的函数,调用的函数也必须为const函数,const函数与普通函数构成重载
{
//i2 = 3; 不能修改i2
//geti2(); 不能调用非const函数
cout << i1 << endl;
cout << i2 << endl; //可以使用非const数据成员
int local = 20;
local = 40; //数据成员外的变量不受const约束
}
int i3;
void geti2()
{
cout << i2;
}
private:
int i2 = 2; //c++11类内初始化(之前版本不允许)等价于成员初始化列表,该成员会默认初始化为指定值,如果构造器初始化列表设定了该成员的初始化则类内初始化不起作用(被覆盖)
};
int mainconst()
{
Classconst c(20);
c.dis(); //非const对象优先调用非const函数,当没有普通函数时调用const函数
const Classconst c2; //const 对象
//错误:c2.geti2(); const对象只能调用const函数
c2.dis(); //调用的是dis() const函数,
c2.i3; //可访问非const数据
//错误:c2.i3 = 30; const对象不能修改任何成员
int i1=10,i2=20;
-i1; //分析int类型变量的operator-() 负号运算符,取负不影响原变量
i1 = -i1; //-i1可以作为右值(用于赋值)
//报错:-i1 = i2; //-i1不可作为左值(不可修改)
-(-i1); //可以对-i1再取负数,而这个过程不能改变-i1本身
//所以-i1产生了一个使用i1数据进行运算的备份,而这个备份const不可修改,但可以对这个const再产生新的备份
return 0;
}
class ClassNum //模拟取负的逻辑
{
public:
int v1;
int v2;
ClassNum(int a, int b) :v1(a), v2(b) {}
//const ClassNum operator-() //返回const对象,对应-c1,返回一个const备份(通过对c1取负),返回对象用到默认的拷贝构造器(不涉及堆内存/动态内存则不需要手动定义)
//{
// return ClassNum(-v1, -v2); //创建一个新的对象,使用当前对象的数据取负。创建对象使用了两个参数的构造器,返回对象使用了拷贝构造器,所以调用时总共会额外创建两个对象
//}
//上面的函数可以完成-c1;c1=-c1;以及不允许-c1=c2三项。但是-(-c1)不行,因为-c1返回了const对象,const对象不能调用非const函数。所以应该修改该函数为const函数
const ClassNum operator-() const //将函数声明为const,普通对象可以访问,const对象也可以访问
{
return ClassNum(-v1, -v2);
}
int norint; //普通成员变量,当对象声明为const时不能修改对象的成员
mutable int mutint; //mutable关键字修饰的成员,即便是在const对象/结构变量中也可以修改
};
class Cow
{
char name[20];
char* hobby;
double weight;
public:
Cow() :name(""), hobby(nullptr), weight(0.0) {} //成员初始化列表,在创建对象、声明成员时进行初始化,执行到函数体{}中对象已经进行了默认的或指定的初始化,在函数体中进行的为赋值而非初始化,通过name("")就可看出,等价于声明成员name=""(char数组只在初始化时可以=字符串常量),而将hobby初始化为nullptr(c++关键字,等价于NULL和0,专门指代空指针),通过列表初始化时不能使用this,也就是说这里的初始化发生在产生对象之前
~Cow() { delete[]hobby; } //hobby==nullptr时也可以delete[]hobby
Cow(const char* nm, const char* ho, double wt) :weight(wt)
{
strncpy(name, nm, 19);
name[19] = 0;
hobby = new char[strlen(ho) + 1];
strcpy(hobby, ho);
}
Cow(const Cow& c) :weight(c.weight)
{
strcpy(name, c.name);
if (c.hobby) //需要判断hobby!=nullptr,虽然为空指针时可以delete[]hobby,但调用strlen等不接收nullptr的函数会出错
{
hobby = new char[strlen(c.hobby) + 1];
strcpy(hobby, c.hobby);
}
else
{
hobby = nullptr;
}
}
Cow& operator=(const Cow& c)
{
delete[]hobby;
weight = c.weight; //赋值运算符重载无法使用成员初始化列表,因为this是已存在对象
strcpy(name, c.name);
if (c.hobby) //如果将hobby默认设置为new char[1],hobby[0]=0则调用strlen不会出错,但默认构造会占用堆内存
{
hobby = new char[strlen(c.hobby) + 1];
strcpy(hobby, c.hobby);
}
else
{
hobby = nullptr;
}
return *this;
}
void ShowCow()const //const函数兼容const对象
{
cout << "name:" << (name[0] ? name : "none") << "\thobby:" << (hobby ? hobby : "none") << "\tweight:" << weight << endl;
}
};
/*------ClassStatic.h头文件--------*/
class ClassStatic
{
public:
~ClassStatic();
static int count; //static数据成员,属于类、唯一,对象可调用,使用前必须在类以外的地方声明
static const int SIZE = 16; //static const 同时修饰数据成员,既属于类、唯一,又不可修改。只能像这样在声明时进行初始化
ClassStatic(char v = '\0')
{
if (count < SIZE) //通过对象调用static成员
stack[count++] = v; //改变static成员
}
static void reset() //static函数成员,属于类,可通过类调用,也可由对象调用,所以没有this指针,也就是说不能在函数中访问属于对象的数据/函数成员,static函数只能访问static数据/函数
{
count = 0;
}
int data;
void func() {}
private:
static char* stack; //static私有成员也在类外声明才能使用,但除声明外不能直接访问(因为私有),类中的static声明就像是名称空间中的引用式声明外部链接变量,如果在头文件中的类声明中使用了定义式声明(初始化一个静态成员或者写一个函数定义),当多个源文件都包含该头文件时会产生多重定义冲突,所以在类声明中只声明,在同名cpp文件中进行定义
int va;
};
/*------ClassStatic.cpp文件--------*/
ClassStatic::~ClassStatic() {} //析构略
int ClassStatic::count = 0; //在cpp文件中完成类的静态成员的定义式声明,同样只能存在一个定义式声明,静态变量不显式初始化会初始化为0。类的静态成员在头文件、源文件中的引用式声明、定义式声明就像名称空间中的静态变量用法一样
char arr[ClassStatic::SIZE]; //类外使用static const常量
char* ClassStatic::stack = arr; //所有static成员都必须在类外声明(包括私有的),在类的头文件配套的cpp文件中声明,除该声明外不能直接访问(因为私有)
/*------用户使用.cpp文件--------*/
int mainstatic()
{
ClassStatic::count = 2; //通过类访问static成员,这时还没有创建对象,static成员属于类
//错误:ClassStatic::stack;私有不可访问
ClassStatic c;
c.reset(); //通过对象调用static函数
c.count = 5; //通过对象调用static数据
ClassStatic::reset(); //通过类调用static函数
//static成员相当于是类的名称空间下的全局变量,全局/静态变量从程序开始到结束一直存在,所以初始化非0的全局/静态变量(RW-data)(read write)随代码(code/text)以及常量(RO-data)(read only 只读的静态/全局变量)保存在ROM(硬盘等)的程序文件中,所以载入文件/程序运行后将全局/静态变量读取到RAM(内存)即可使用,而未初始化或初始化为0的全局/静态变量(ZI-data/.bss)(zero init)只需要记录大小,在程序运行前开辟好空间备用。所以全局/静态变量的大小是固定的,程序执行之前就知道应该开辟多大,也就不存在溢出的问题
//而普通数据成员属于对象,在函数/栈中创建自动类型变量时每个对象大小为其非静态数据成员的总和,由于栈有最大限制,如果栈内变量过多(如大数组)可能导致栈溢出。
//动态内存/堆的大小随malloc/free等函数而变化
int ClassStatic::* pd = &ClassStatic::data; //声明一个指向普通数据成员的指针,pd的类型为(int ClassStatic::*) 整体
//作用域解析运算符优先级最高,所以赋值运算符右侧等价于&(ClassStatic::data)
//在这个声明中没有使用某一个对象,所以成员指针pd只记录了成员在类中的位置
c.data = 5; //正常调用
c.*pd = 10; //通过指针调用,这里运算符 .* 为一个运算符,pd实际记录了成员在类中的偏移量信息,c.*pd通过计算偏移量访问指向的区域
//普通的指针 类型名 * 指针=&变量
//指向成员的 类型名 类名::* 指针 = &类名::成员
//成员指针可以更改指向相同类型的成员,这里pd类型为(int ClassStatic::*),可以指向类ClassStatic中任意int数据成员
void (ClassStatic::* pf)() = &ClassStatic::func; //声明指向函数成员的指针
//类名::* 是一个整体,需要和函数成员指针括在一起,即 函数签名部分 (类名::* 指针)函数签名部分。给函数成员指针赋值必须使用 &函数名 取址,不同于普通函数名赋值
c.func(); //正常调用
(c.*pf)(); //通过指针调用,运算符.*优先级低于(),需要将 对象.*指针 作为整体括起来
//普通的指针 类型名 (*指针)() = [&]函数名
//指向成员的 类型名 (类名::*指针)() = &类名::函数
ClassStatic* pc = new ClassStatic; //在堆中创建对象
pc->*pd; //运算符->* 同样为一个整体
(pc->*pf)(); //运算符->*优先级同样低于()
//(this->*pf)() 如果是在成员函数内也可以使用this指针加成员指针使用
//结构的大小是成员的和,结构访问成员是 结构名.字段名 可以理解为 结构变量的地址+偏移量 按类型进行读取和解释
//类的大小是成员的和,可以理解为 对象名.成员名 同样是对象地址+偏移量 按类型大小进行读取,按类型进行解释
//.* ->* 也理解为偏移量
delete pc;
return 0;
}
class ClassFriend
{
public:
enum
{
FUNC_ARRAY_SIZE = 5, MAX_ITEM = 1024
}; //在类内使用枚举,把类中需要使用的常量集中管理,优于逐个const static,方便查找修改,不需要命名枚举类型,这个枚举属于类,不占对象空间,类外通过类::访问
void (*func[FUNC_ARRAY_SIZE])(); //效果同func[5]
friend void getmember(ClassFriend& obj); //友元函数,通过友元可无视访问限制直接访问对象的私有、公有成员,在其他名称空间定义、使用,这里声明的是全局函数,等价于 ::getmember
private:
int mem;
};
void getmember(ClassFriend& obj) //把友元函数定义为全局函数,因为不属于ClassFriend类及对象(即没有this指针),所以需要把对象传进来
{
obj.mem = 20;
cout << obj.mem << endl;
//友元全局函数需要在类中声明,将函数原型写在类中,不在类外再次声明函数原型,如果在类中直接定义友元全局函数则为内联的友元全局函数
}
int mainfriend1()
{
ClassFriend c;
//错误:c.mem = 30;私有不可访问
getmember(c); //通过友元函数访问
return 0;
}
class ClassFriend1; //前向声明
class ClassFManage1 //把友元成员函数定义在另一个类中
{
public:
void getmember(ClassFriend1& obj); //通过CFM1对象.getmember()访问CF1对象的私有成员,因为函数形参中需要用到ClassFriend1类型,所以需要在上面前向声明这个类
};
class ClassFriend1
{
private:
friend void ClassFManage1::getmember(ClassFriend1& obj); //CFM1类的函数所以要写名称空间,因为用到了CFM1::getmember()函数,所以要在上面声明这个函数,这里不能只在上面写类{这个函数的声明}而在下面写完整的类声明。只能将友元函数所属的类完整声明在上面。这意味着两个类不能相互包含友元成员函数
//friend关键字不受private等限制,可写在类中任意位置不受影响
int mem;
};
void ClassFManage1::getmember(ClassFriend1& obj) //友元函数的实现因为要用到CF1对象的属性,所以又必须放在CF1类的声明的下面(因为前向声明没有声明数据成员)
{
obj.mem = 30;
cout << obj.mem << endl;
}
//所以书写顺序为:先引用CF1类-再声明CFM1类-再声明CF1类(内含friend CFM1::函数)-再实现CFM1::函数
int mainfriend2()
{
ClassFriend1 c;
ClassFManage1 m;
m.getmember(c); //通过m访问c的私有成员
return 0;
}
class ClassFriend2
{
public:
friend class ClassFManage2; //友元类,使用友元类则不需要前面先声明类CFM再在前面引用类CF
private:
int mem;
};
class ClassFManage2 //友元类中所有函数都可以通过CF2对象访问成员
{
public:
void getmember(ClassFriend2& obj);
};
void ClassFManage2::getmember(ClassFriend2& obj)
{
obj.mem = 40;
cout << obj.mem << endl;
}
int mainfriend3()
{
ClassFriend2 c;
ClassFManage2 m;
m.getmember(c);
return 0;
}
class ClassFriend3
{
public:
friend ClassFriend3 operator+(ClassFriend3& c1, ClassFriend3& c2); //使用友元重载+运算符
//ClassFriend3 operator+(ClassFriend3& c); //之前使用类的函数实现+运算符,两种方法会产生二义性,因为表现形式均为c1+c2
private:
int mem;
};
class ClassFriend4
{
double value_;
public:
ClassFriend4(double value = 0) :value_(value) {}
void display1()const { cout << value_ << endl; } //公共接口,打印值,因为不修改*this对象所以函数const以兼容const对象
friend void display2(const ClassFriend4& cf4) { cout << cf4.value_ << endl; } //友元全局内联函数,通过友元访问私有成员
};
inline void display3(const ClassFriend4& cf4) { cf4.display1(); } //内联的非友元函数,通过调用类接口函数来访问私有成员
class CLSFa
{
friend class CLSFb; //fa和fb相互为友元
int da;
public:
void setfb(CLSFb& obj); //因为前面有friend声明所以参数可以使用CLSFb类型,但这里该类的成员不可见,所以要把定义放在CLSFb类声明的后面或者.cpp文件中,如果想内联可以使用inline定义
};
class CLSFb
{
friend class CLSFa; //互相友元
int db;
public:
void setfa(CLSFa& obj) //因为上面有CLSFa类的成员的声明,所以这里可以直接写定义
{
obj.da = 20;
}
};
inline void CLSFa::setfb(CLSFb& obj) //通过inline内联
{
obj.db = 30;
}
//声明两个类共同的友元
class CLSfs1; //前向声明其中一个类
class CLSfs2
{
friend void sync1to2(const CLSfs1& fs1, CLSfs2& fs2); //1to2需要两个类的类型,因为是friend声明在一个类中了所以还需要另一个前向声明,通过这个语句只能知道1to2可以访问fs2的私有
friend void sync2to1(const CLSfs2& fs2, CLSfs1& fs1); //调换参数顺序
int t=20;
};
class CLSfs1
{
friend void sync1to2(const CLSfs1& fs1, CLSfs2& fs2); //到这里才知道函数既可访问fs2私有也可访问fs1私有
friend void sync2to1(const CLSfs2& fs2, CLSfs1& fs1);
int t=30;
};
void sync1to2(const CLSfs1& fs1, CLSfs2& fs2) //全局友元
{
fs2.t = fs1.t;
}
void sync2to1(const CLSfs2& fs2, CLSfs1& fs1)
{
fs1.t = fs2.t;
}