C++数据的共享和保护
对象生命周期
静态生命周期
定义:**如果对象的生命周期于程序的运行期相同,那么它就是具有静态生命周期。**在文件作用域中声明的对象都具有静态生命周期。(通俗点,就是全局变量是是静态的,直到程序运行结束才死亡),如果在函数中的局部作用域中声明具有静态生命周期的对象,则要加上关键字static。(通俗点,加了staitic的变量是静态变量,直到程序运行结束才死亡)。这个在C语言中也有过这样的定义。在C++中仍然沿用这样的定义方式。
上代码:
#include<iostream>
using namespace std;
//静态变量只初始化一次
void Fun()
{
static int k = 9;
cout << k++ << endl;
}
class MM
{
public:
//这里使用了友元和运算符重载
friend ostream& operator<<(ostream& out, MM mm);
protected:
string name;
int age;
};
MM mm;
int i; //在文件作用域,属于全局变量(对象),没有初始化,默认初始化为零
int main()
{
cout << i << endl;
static int j = 3; //定义了一个静态的整型变量j并且初始化为3
cout << j << endl;
Fun(); //打印9
cout << mm << endl;
//输出: 0
//静态变量(对象)只初始化一次,如果没有初始化,这默认为零,根据类型自行进行隐式转换
Fun(); //打印10
return 0;
}
ostream& operator<<(ostream& out, MM mm)
{
out << mm.name << '\t' << mm.age << endl;
return out;
}
动态生命周期
非静态生命周期就是动态生命周期
在函数作用域中声明的变量就是动态的,在局部作用域中声明的变量具有动态生命周期,在声明处诞生,在声明所在的块指向完毕就死亡。动态声明的变量没有初始化其初值未定是一个垃圾值,即上次使用到这个内存的数据是多少就是多少,一般来说,数字的垃圾值是负数,字符的垃圾值是"烫烫烫"等中文样式。
上代码
#include<iostream>
using namespace std;
void testFun()
{
int i = 9;// 定义了一个动态的整型变量i,初始为9
//下一次进来的时候会在一次这样定义这个变量
cout << i++ << endl;
}
void testFun2()
{
int k; //没有初始化,存放的是垃圾值,一般是负数
cout<<k++<<endl;
}
int main()
{
testFun(); //打印:9
testFun(); //打印:9
testFun(); //打印:9
testFun2(); //垃圾值
testFun2(); //垃圾值
testFun2(); //垃圾值
return 0;
}
类的静态成员
如果某个类的属性是整个类共有的,而不属于任何一个具体的对象,则用static关键字类声明为静态成员。
简单来说就是在类中的成员使用了静态性质,那么这个静态的成员就是属于整个类,而不是某一个类的对象独有的。而静态则用static 修饰。
**类的静态成员只能在类中声明在类外实现。包括属性和行为。**之所以要这样做,是因为要给静态成员单独分配一个空间,这个空间是整个类公有。
举个简单的例子:
在学生类中,有姓名和年龄等属性是单个对象特有的,不同的学生有不同的姓名,也要自己对应的年龄,当他们修改了自己的姓名并不影响到其他学生的姓名;修改年龄也是一样的,就像是你本来是20岁,然后你骗别人说你18岁,于是你自己改了自己的年龄,但对别人来说,比如的年龄还是别人原来的年龄。但是当学生类中有一个静态的成员就不一样了,这个静态成员是学生类共有的。就比如教室里的黑板,每个学生都有权力在这个黑板上写字,每写上一个新的东西就代表了这个黑板是改变的,每次都长得不一样嘛。所以当在黑板上写上“某某某喜欢某某某”的时候,在其他学生的眼中是可以看得到的。所以说静态成员就是这个意思。没有加static关键字的成员就是自己特有的,加了static关键字就是整个类共有的。
类的静态数据成员(类的静态属性)
静态的数据项(静态属性)属于整个类,不属于类的某个对象。类的数据成员必须是在类中声明,在类外初始化。访问可以不需要对象,但是需要类名限定,同时也是受类的权限限定。
#include<iostream>
using namespace std;
class Student
{
public:
string name; //学生姓名 单个学生类的对象自己有
int Stuclass; //学生班级 单个学生类的对象自己有
static int blackboard; //黑板 全体学生类的对象共有
};
//静态成员必须要在类外初始化,而且必须用类名标识,并且不需要关键字static
int Student::blackboard = 4;
int main()
{
Student Cukor; //创建了一个学生类的对象
Student Jack; //又创建了一个学生类的对象
cout << "Cukor:" << Cukor.blackboard << '\t' << "Jack:" << Jack.blackboard << endl;
//同时访问这两个对象中的静态属性,结果都是一样的
//下面的代码是通过其中的一个对象去修改这个静态属性的内容,然后再用这两个对象访问这个静态属性
Cukor.blackboard = 99;
cout << "Cukor:" << Cukor.blackboard << '\t' << "Jack:" << Jack.blackboard << endl;
//结果法相两个对象的静态属性的内容都是一样的,也就是可以证明静态属性是类的所有的对象共有
//再创建第三个学生类的对象,出来再去访问静态属性的时候也的得到一样的结果
Student Tom;
cout <<"Tom: "<<Tom.blackboard << endl;
return 0;
}
静态函数成员(类的静态行为)
行为就是一个动作吧。那么在C++中就是用函数去实现。当使用了static关键字修饰的类的成员函数,那么这个函数就是整个类共有的函数。再继续用上面的例子,上面说了黑板是整个学生类共有的属性,那么这里就再加一个“擦黑板”的行为。我们想让学生去擦教室里黑板(原本的黑板上写了很多东西,现在让学生去擦)。那现在,Cukor这个同学上去把黑板擦了,Jack看到黑板已经被擦干净了,那Jack就没有必要再去擦黑板了吧。如果Cukor只是擦了自己的书桌,Cukor的书桌干净了,但对于Jack来说Jack的书桌还是杂乱无章的。那Cukor擦自己的书桌的行为就不影响到Jack,所以Jack也想要自己的桌面整洁,那就得自己动手。那么这个例子中呢,擦黑板就是静态的,是大家共有的;而擦桌子是动态的,是特有的。
静态函数成员的函数内部不能使用this指针,因为this指针是实例对象的地址,在对象没创建出来是抽象的(不存在的),而因为静态函数成员属于整个类,所以this指针不知道指的是哪个具体的对象。
#include<iostream>
using namespace std;
class Student
{
public:
string name; //学生姓名 单个学生类的对象自己有
int Stuclass; //学生班级 单个学生类的对象自己有
//静态成员函数可以来类中实现
static void ourBK()
{
blackboard *= -1;
if (blackboard == 1)
{
cout << "黑板已经擦干净" << endl;
}
else
{
cout << "黑板上写了\"某某某我喜欢你\"" << endl;
}
}
//静态成员函数可以在类中声明在类外实现
static void print();
protected:
static int blackboard; //黑板 全体学生类的对象共有
};
int Student::blackboard = 1;
int main()
{
Student Cukor;
Student Jack;
//使用对象访问
Cukor.ourBK();
//不使用对象访问
Student::ourBK();
Jack.ourBK();
Cukor.ourBK();
return 0;
}
void Student::print()
{
cout << "this is Student::print" << endl;
}
/*
输出结果:
黑板上写了"某某某我喜欢你"
黑板已经擦干净
黑板上写了"某某某我喜欢你"
黑板已经擦干净
*/
通过上面的代码就可以证明,静态函数成员是整个类共有的,不算某一个对象特有的。
类的友元
友元相当于朋友,一个东西被声明成友元,那么这个东西就可以访问得到这个类所有东西,就像是你交了一个好朋友,然后你就毫无保留的告诉你这个好朋友你的一切一样。C++的friend关键字是这样,但是现实生活中还是不要这样好,保留自己的一些隐私比较好,要不然显得自己很廉价。
友元的关键字:friend
被声明成友元的东西都不属于该类,声明为友元的作用主要是想打破类的权限限定,直接访问到类中的所有数据。
友元函数
简单来说,友元函数就是在类中用friend关键字修饰的函数就这个类的友元函数。
注意点:声明友元函数的时候,在友元函数的参数列表中要有对应的类的对象。
#include<iostream>
using namespace std;
class MM
{
public:
//下面这个函数是MM类的友元函数
friend void print(MM mm) //参数列表里是MM类的对象,然后mm可以访问MM类的全部内容
{
//友元函数可以在类中实现,也可以在类外实现
mm.name = "This is my friend"; //这里的访问是正确的,因为声明了是友元函数
cout << mm.name << endl;
mm.age = 18;
cout << "He or she is " << mm.age << "years old" << endl;
}
friend void run(MM mm);
protected:
string name="Alice";
int age;
};
//类外实现的时候,去掉friend关键字
void run(MM mm)
{
//这里的访问是正确的
cout << mm.name << "跑起来了" << endl;
}
int main()
{
MM mm;
//单纯的想在这里访问MM类中的name是不可以的
//mm.name="美女"; 是错的
//mm.age=19; 也是错的
print(mm);
run(mm);
return 0;
}
友元类
跟友元函数一样,一个类可以声明另一个类是本类的友元类。若A类是B类的友元类,那么A类的所有成员函数都是B类的友元函数,都可以访问得到B类里面的所有东西。
class B
{
...
friend class A; //A类是B类的友元类
...
};
下面是测试代码
#include<iostream>
using namespace std;
class MM
{
public:
friend class GG; //声明GG类是MM类的友元,即GG和MM是朋友
MM(string name,int age,int faceScore):name(name),age(age),faceScore(faceScore){}
void printMM();
protected:
string name;
int age;
int faceScore;
};
class GG
{
public:
GG(string name,int age,int faceScore):name(name),age(age),faceScore(faceScore){}
void callMM(MM mm);
protected:
string name;
int age;
int faceScore;
};
int main()
{
MM mm("Alice", 19, 100);
GG gg("Cukor", 20, 60);
gg.callMM(mm);
return 0;
}
void MM::printMM()
{
cout << "MM的信息如下:" << endl;
cout << "姓名: " << this->name << endl;
cout << "年龄: " << this->age << endl;
cout << "颜值: " << this->faceScore << endl;
}
void GG::callMM(MM mm)
{
//访问MM类中的数据成员没有任何问题
cout << this->name << "叫" << mm.name << "出来玩" << endl;
mm.printMM(); //调用MM类的成员函数,没有任何问题
}
关于友元还需要注意几点:
- 友元关系不能传递:A是B的友元,B是C的友元,但如果没有声明A是C的友元的情况下,A和C没有任何关系。就像是生活中的例子一样,你和Cukor是朋友,Cukor和Jack是朋友,但是你和Jack不认识,那你和Jack就不是朋友。虽然你和Jack的共同朋友是Cukor。
- 友元关系的单项的:A是B的友元,但B不一定是A的友元,除非声明。这个就像是在现实生活中的一样,小明拿小刚当朋友,小刚却不认为小明是自己的朋友。
- 友元关系不会被继承:A是B的友元,A的派生类C和B不是友元关系。生活中的例子就是,小明和小刚是朋友,然后小明的儿子小强和小刚就不是朋友,当然如果声明了,是可以成为朋友的。但一生下来的时候肯定不是吧。
#include<iostream>
using namespace std;
class MM
{
public:
friend class GG;
void printMM()
{
cout << this->name << endl;
}
protected:
string name;
};
class GG
{
public:
void callMM(MM mm)
{
//可以访问得到
cout << this->name << "call"<<mm.name;
mm.printMM();
}
protected:
string name;
};
class Son :public GG
{
public:
void print(MM mm)
{
//访问不到,错误
//cout << mm.name << endl;
}
protected:
int age;
};
int main()
{
return 0;
}
类的常成员
使用const修饰的量就是一个常量,在初始化后就不能再改变。所以说一定要初始化。目的很简单,就是为了保护数据,防止数据不小心在程序运行的过程中改变。起到了保护的作用。
常数据成员
和一般的数据类型一样,在前面加了const修饰,那么这个数据成员就是一个常属性,定义的时候初始化,之后就不能修改了。
在类中的常数据成员只有两种初始化方式:
- 直接在定义的时候初始化
- 通过构造函数的初始化参数列表进行初始化
#include<iostream>
using namespace std;
class MM
{
public:
MM(string name,int age):name(name),age(age){}
MM(string name):name(name){}
void print()
{
cout << name << '\t' << age << endl;
}
protected:
const string name;
const int age = 18;
};
int main()
{
MM mm("Alice", 19);
mm.print(); //打印:Alice 19
MM mm2("Coco");
mm2.print(); //打印:Coco 18
return 0;
}
常函数成员
使用const修饰的函数成员就是常函数成员,常函数成员有以下特性:
- 在当前函数中不能修改数据成员,无论是什么数据
- const写在相应的函数的后面
- 可以和普通的函数成员同时存在
- 普通对象优先调用普通函数,没得选择的时候才会调用常函数成员
- 常对象只能调用常函数成员
#include<iostream>
using namespace std;
class MM
{
public:
//使用初始化参数列表进行初始化
MM(string name,int age):name(name),age(age){}
//普通函数成员
void print()
{
age++;
cout << name << '\t' << age << endl;
}
//常函数成员
void print()const
{
//age++; 这里会报错,因为常函数成员中的数据不能被改
cout << name << '\t' << age << endl;
}
protected:
const string name;
int age;
};
int main()
{
MM mm("Alice", 19);
mm.print(); //调用的是普通成员函数
//打印:Alice 20
const MM mm2("Coco", 19);
mm2.print(); //调用的是常函数成员,因为mm2是常对象
return 0;
}
常对象
使用const修饰的对象就是常对象。
const int i=100; //这个是一个常对象
//假设这里有一个MM类
const MM mm; //这个是一个常对象
MM const mm2; //这样写也是正确的,一般使用上面的写法
常对象是在定义时初始化,然后在这个对象的生命周期期间不能再修改对象里面的属性。常队形必须进行初始化。
常对象只能调用常函数成员,不能调用非常函数成员。
#include<iostream>
using namespace std;
class MM
{
public:
MM(){}
MM(string name,int faceScore):name(name),faceScore(faceScore){}
void print()const
{
cout << "常函数成员" << endl;
cout << name << '\t' << faceScore << endl;
}
void print()
{
cout << "普通函数" << endl;
cout << name << '\t' << faceScore << endl;
}
void printData()
{
cout << "this is printData" << endl;
cout << name << '\t' << faceScore << endl;
}
protected:
string name;
int faceScore;
};
int main()
{
const MM mm;
MM const mm2;
mm.print(); //打印的是垃圾值
mm2.print(); //打印的是垃圾值
const MM mm3("Alice", 100);
mm3.print();
//mm3.printData();
//错误:对象含有与成员 函数 "MM::printData" 不兼容的类型限定符
MM mm4("Coco", 80); //定义一个普通对象
mm4.printData(); //正确
return 0;
}
|