> 发现更多的计算机知识,欢迎访问xiaocr的个人网站
引言:
本文是作者在学习C++的数据的共享和保护过程中总结归纳的,写成博客既是一种对自己的提醒,更是分享给更多的伙伴学习交流。如若本文有些许漏洞抑或是差错,欢迎批评指正,也欢迎补充一起交流学习。配合目录适用更佳,相信会对你有所帮助。话不多说,首先看看下面的思维导图,它囊括了本文的大部分内容,让我们有一个更好的思维框架。(如下图)
1.1标识符的作用域和可见性
首先要搞清楚概念:
作用域讨论的是标识符的有效范围,可见性讨论的是标识符是否可以被引用
1.1.1作用域
作用域是一给标识符在程序正文中有效的区域。
注意:
1.函数原型作用域
- 函数原型作用域是C++程序中最小的作用域。
- 函数原型声明时形参的作用范围就是函数原型的作用域。
例子:
double area(double radisus)
标识符radisus的作用范围就是左右括号之间,在程序的其他地方不能引用这个标识符。
注意一点:
2.局部作用域:
首先拥有局部作用域的变量就是我们俗称的局部变量。函数体内声明的变量从声明处开始,一直到声明所在的块结束的花括号为止。方便理解,我们看一个例子:
3.类作用域
顾名思义,类作用域就是类内声明的变量的有效范围。这里介绍三种访问方式。
A. 在X的成员函数中若没有声明同名的局部作用域标识符,那么在该该函数内可以直接访问m_X
B.通过表达式x.m_X或者X::M_X。其中后面一种的方式适用于访问类的静态成员,具体内容后面介绍。
C。通过ptr->M_x的方式,其中ptr为指向X类的一个对象的指针.
4.文件作用域
这个就是全局变量。这样的标识符都是结束于文件尾。
5.命名空间作用域
命名空间的定义使用namespace关键字。声明的方式如下:
namespace namespace_name{
//代码声明
}
重点:namespace的引入可以避免大项目协同合作时候产生的命名冲突
以及常见于我们CPP文件开头的
using namespace std;
这样就是是的标准命名空间中的实体调用无需加空间前缀std::
6.限定作用域的enum枚举类
这个就看一些实例了解一下:
1.1.2可见性
之前我们说了,可见性其实就是这个标识符是否可以在一定范围内被引用。
作用域关系图:
可见性的一般规则如下:
- 标识符要声明在前,引用在后
- 在同一作用域中,不能命名相同的标识符
- 在没有互相包含的不同作用域中,标识符是否同名不受影响
- 对于作用域,假若内部和外部具有相同的变量名称。此时外部的变量对内部而言是不可见的。
提示:
1.2对象的生存期
对象(包括简单变量)都有诞生和消失的时刻。对象从诞生到结束的这段时间就是它的生存期。在生存期内,对象将保持它状态(即数据成员的值),变量也将保持它的值不变,直到被更新
生存期又分为静态生存期和动态生存期。
1.2.1静态生存期
如果对象的生存期和程序的运行期相同的话,我们称它具有静态生存期。
如果要在函数内部的局部作用域之声明静态生存周期的对象,要使用static关键字
static int i;//i具有静态生存周期
特点:
- 它并不会随着每次函数调用而产生一个副本.
- 不会随着函数返回而失效,也就是说,当一个函数返回后,下一次再调用时,该变量还会保持上-
回的值. - 即使发生了递归调用,也不会为该变量建立新的副本,该变量会在各次调用间共享
注意一点:
静态变量未初始化的话默认为0,而动态变量的值不确定。
1.2.2动态生存期
动态生成期就是非静态的,不具有文件作用域。
说了这么多,看一个完整例子:
运行结果:
1.3类的静态成员
在结构化程序设计中程序模块的基本单位是函数,因此模块间对内存中数据的共享是通过函数与函数之间的数据共享来实现的.其中包括两个途径参数传递和全局变量。static的引入这样:
-
一方面在类内部的函数之间实现了数据的共享.
-
另一方面这种共享是受限制的.可以设置适当的访问控制属性,把共享只限制在类的范围之内.对类外来说,类的数据成员仍是隐藏的达到了共享与隐藏两全
1.3.1静态数据成员
在面向对象的方法中有类属性的概念。如果某个属性为整个类所共有,不属于任何一个具体对象,则采用static关键字来声明为静态成员。
类属性是描述类的所有对象共同特征的一个数据项.对于任何对象实例.它的属性值是相同的
静态数据成员具有静态生存期,就是文件作用域。一般的用法是:
Class_Name :: 标识符
但是注意一点很重要的:
之所以类的静态数据成员需要在类定义之外再加以定义,是因为需要以这种方式专门为它们分配空间。非静态数据成员无须以此方式定义,是因为它们的空间是与它们所属对象的空间同时分配的
看一个例子:
这个代码很好的展示了如何计数对象个数:
#include<iostream>
using namespace std;
//Point类定义
class Point {
//外部接口
public:
//如果在 Point 类的构造函数中没有为参数 x 和 y 提供默认值,那么在创建 Point 对象时,必须为 x 和 y 指定具体的值,否则会编译错误。
Point(int x = 0, int y = 0) : x(x), y(y) {//构造函数
//在构造雨数中对 ccant 累加,所有对象共同维护同一个 count
count++;
}
//复制构造函数
Point(Point& p)
{
x = p.x;
y = p.y;
count++;
}
~Point() {
count--;
}
int getx() { return x; }
int getY() { return y; }
//输出静态数据成员
void showCount() {
cout << " object count=" << count << endl;
}
//私有数据成员
private:
int x, y;
//静态数据成员声明,用于记录点的个数
static int count;
//常量静态成员类内初始化
constexpr static int origin = 0;
};
//静态数据成员定义和初始化,使用类名限定
int Point::count = 0;
// 类外定义常量静态成员, 但不可二次初始化
constexpr int Point::origin;
//主函数
int main() {
//定义对象a,其构造函数会使count增1
Point a(4,5);
cout << "Point A: " << a.getx() << ","<<a.getY();
//输出对象个数
a.showCount();
//定义对象b,其构造函数会使count增1
Point b(a);
cout << "Point B: " << b.getx() << ","<<b.getY();//输出对象个数
b.showCount();
return 0;
}
输出结果:
附上一张便于理解:
1.3.2静态函数成员
在上面那段代码中,我们也许会想着,我们能不能直接调用showCount函数呢.像这样:
Point :: showCount();//直接通过类名调用
但是这个虽然简单,但是编译会报错。这是因为对普通的函数成员调用必须要使用对象名。
因此我们引入静态成员函数来满足我们的需求。静态成员函数可以直接访问该类的静态数据和函数成员。而访问非静态成员.必须通过对象名。请看下面的程序段
classA{
public:
static void f(Aa);
private:
int x;
void A:: f(A a)
//对×的引用是错误的
cout<<x<<emdl;
//正确
cout<<a.x;
注意:
之所以在静态成员函数中访问类的非静态成员需要指明对象.是因为对静态成员函数的调用是**没有目的对象的.**因此不能像非静态成员函数那样.隐含地通过目的对象访问类的非静态成员
看这个例子感悟一下:
#include<iostream>
using namespace std;
//Point类定义
class Point {
//外部接口
public:
//如果在 Point 类的构造函数中没有为参数 x 和 y 提供默认值,那么在创建 Point 对象时,必须为 x 和 y 指定具体的值,否则会编译错误。
Point(int x = 0, int y = 0) : x(x), y(y) {//构造函数
//在构造雨数中对 ccant 累加,所有对象共同维护同一个 count
count++;
}
//复制构造函数
Point(Point& p)
{
x = p.x;
y = p.y;
count++;
}
~Point() {
count--;
}
int getx() { return x; }
int getY() { return y; }
//输出静态数据成员
static void showCount() {
cout << " object count=" << count << endl;
}
//私有数据成员
private:
int x, y;
//静态数据成员声明,用于记录点的个数
static int count;
//常量静态成员类内初始化
constexpr static int origin = 0;
};
//静态数据成员定义和初始化,使用类名限定
int Point::count = 0;
// 类外定义常量静态成员, 但不可二次初始化
constexpr int Point::origin;
//主函数
int main() {
//定义对象a,其构造函数会使count增1
Point a(4, 5);
cout << "Point A: " << a.getx() << "," << a.getY();
//输出对象个数
Point::showCount();
//定义对象b,其构造函数会使count增1
Point b(a);
cout << "Point B: " << b.getx() << "," << b.getY();//输出对象个数
Point::showCount();
return 0;
}
1.4类的友元、
C++很有意思。不仅有这许许多多严格的规定,还有着不少的例外。甚至专门引入新东西来破坏规则。友元就是这么样的。友元有友元函数和友元类。
友元关系提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。通俗地说.友元关系就是一个类主动声明哪些其他类或函数是它的朋友.进而给它们提供对类的访问特许。也就是说.通过友元关系,一个普通函数或者类的成员函数可以访问封装于另外一个类中数据
我们并不推荐使用这个!
1.4.1友元函数
友元函数是在类中使用关键词friend修饰的非成员函数。在它函数体内可以通过对象名访问类的私有和保护成员
friend fuc_type fuc_name(parameter list)
友元函数不仅可以是个普通函数.也可以是另外的使用和一般友元函数的使用基本相同,只是要通过相应的类或对象名来访问
1.4.2友元类
同友元函数一样,一个类可以将另一个类声明为友元类。若A类为B类的友元类,则
A类的所有成员函数都是B类的友元函数.都可以访问B类的私有和保护成员。
声明友元
类的语法形式为:
class B{
//B类的成员声明
……
//声明A为B的友元类
friend class A;
……
}
声明友元类.是建立类与类之间的联系,实现类之间数据共享的一种途径。
1.5共享数据的保护
虽然数据隐藏保证了数据的安全性.但各种形式的数据共享却又不同程度地破坏了数据的安全。因此.对于既需要共享、又需要防止改变的数据应该声明为常量。因为常量在程序运行期间是不可改变的,所以可以有效地保护数据
1.5.1常对象
常对象是这样的对象.它的数据成员值在对象的整个生存期间内不能被改变。也就是说,常对象必须进行初始化.而且不能被更新。声明常对象的语法形式为:
const 类型说明符 对象名;
与基本数据类型的常量相似.常对象的值也是不能被改变的
注意:
**在定义一个变量或常量时为它指定初值叫作初始化,而在定义一个变量或常量以后使用赋值运算符修改它的值叫作赋值.请勿将初始化与赋值混淆。
补充一点:
基本数据类型的常量也可看作一种特殊的常对象。因此可不再对基本数
据类型的常量和类类型的常对象加以区分
1.5.2用const修饰的类成员
1.常成员函数
使用const关键字修饰的函数为常成员函数.常成员函数声明的格式如下:
类型说明符 函数名 (参数表) const;
注意(供参考):
代码实例:
#include<iostream>
using namespace std;
class R {
public:
R (int r1, int r2) : r1(r1), r2(r2) {}
void print();
void print() const;
private:
int r1, r2;
};
//成员中不允许限定名
void R::print() {
cout << r1 << ":" << r2 << endl;
}
void R::print() const {
cout << r1 << ": " << r2 << endl;
}
int main() {
R a(5, 4);
//调用void print()
a.print();
const R b(20, 52);
//调用void print() const
b.print();
return 0;
}
输出结果;
1.5.4常引用
如果在声明引用时用const修饰,被声明的引用就是常引用。常引用所引用的对象不能被更新。如果常引用做形参,便不会意外地发生对实参的修改。
语法:
const 类型说明符号 &引用名
非const的引用只能绑定到普通的对象,而不能绑定到常对象,但常引用可以绑定到常一个常引用.无论绑定到一个普通的对象.还是常对象.通过该引用访同该对象时,都对象。只能把该对象当作常对象,这意味着.对于基本数据类型的引用.则不能为数据赋值,对于类类型的引用,则不能修政它的数据成员,也不能调用它的非const的成员雨数.
关于C++数据的保护和共享的知识就介绍到这里。CC++语言是适合于编写大型复杂程序的语言.数据的共享与保护机制是C++语言的重要特性之一。
希望这篇文章对你有所帮助!!!