数据的共享与保护-C++
*数据的共享与保护:
- 1.作用域:
- 作用域是一个标识符在程序正文中有效的区域。C++中标识符的作用域有函数原型作用域、局部作用域(块作用域)、类作用域和命名空间作用域。
- (1).函数原型作用域:
- 函数原型作用域是C++中最小的作用域,在函数原型中一定要包含形参的类型说明。在函数原型声明时形式参数的作用范围就是函数原型的作用域。如:double area(double radius);标识符radius的作用范围就在函数area形参列表的括号之间。
- 由于在函数原型的形参列表中起作用的只是形参类型,标识符并不起作用,因此在程序中是允许省去的,但是为了程序的可读性,通常还是要在函数原型声明时给出形参标识符。
- (2).局部作用域:
- 函数形参列表中形参的作用域,从形参列表中的声明处开始,到整个函数体结束为止;函数体内声明的变量,其作用域从声明处开始,一直到声明所在块结束的大括号为止;具有局部作用域的变量也称为局部变量。
- (3).类作用域:
- 类可以看作是一组有名成员的集合,类x的成员m具有类作用域,对m的访问方式有3中:如果在x的成员函数中没有声明同名的局部作用域的标识符,那么在该函数内可以直接访问成员m;通过表达式x.m或者x::m。即程序中访问对象成员的最基本方法;通过ptr->m这样的表达式,其中ptr为指向x类的一个对象的指针。
- (4).命名空间作用域:
- 一个大型的程序通常有不同模块构成,不同的模块有可能有不同人员开发的。不同模块中的类和函数之间可能发生重名。命名空间就会消除这些错误。语法结构:
- namespace 命名空间名{
- //命名空间内的各种声明(函数声明、类声明、。。。);
- }
- 一个命名空间确定了一个命名空间作用域,凡是在该命名空间之内声明的、不属于前面所说各个作用域的标识符,都属于该命名空间作用域。在命名空间内部可以直接引用当前命名空间中声明的标识符,如果需要引用其他命名空间的标识符,需要使用下面语法:
- 命名空间::标识符
- namespace someNs{
- class SomeClass{...};
- };
- 如果要引用类名SomeClass或函数名SomeFunc,需要使用下面的方式:
- someNs::SomeClass obj;
- 有时,在标识符前面总使用这样的命名空间限定会显得过于冗长,为了解决这个问题,C++又提供了using 语句,using语句有两种:
- using 命名空间::标识符;
- using namespace 命名空间名;
- 命名空间也允许嵌套:
- namespace OuterNs{
- namespace InnerNs{
- class SomeClass{...};
- }
- }
- 引用其中的SomeClass类,需要使用OuterNs::InnerNs::SomeClass的语法;
- 此外,还有两种比较特殊的命名空间:
- 全局命名空间和匿名命名空间。全局命名空间是默认的命名空间,在显示声明的命名空间之外声明的标识符都在一个全局命名空间中,匿名命名空间是在一个需要显示声明的没有名字的命名空间。声明如下:
- namespace{
- 匿名命名空间内的各种声明(函数声明、类声明、...);
- }
- 具有命名空间作用域的变量又称为全局变量;
- 2.对象的生存期:
- (1).静态生存期:
- 如果对象的生存期与程序的运行期相同,则称它具有静态生存期;在命名空间作用域中声明的对象都是具有静态生存期的。如果在函数内部的局部作用域中声明具有静态生存期的对象,则要使用关键字static。如:static int i;
- 局部作用域中的静态变量的额特点是:它并不随着每次函数调用而产生副本,也不会随着函数返回而失效。也就是说当一个函数返回后,下一个再调用时,该变量还是上一次的值。即使发生递归调用也不会为该变量建立新的副本,该变量会在每次调用间共享。
- (2).动态生存期:
- 局部生存期对象诞生于声明点,结束声明所在的执行完毕之时。类成员对象也有自己的生存期。不用static修饰的成员对象其生存期都与它们所属对象的生存期保持一致。
- 3.类的静态成员:
- 在结构化程序设计中程序模块的基本单位是函数,因此模块间对内存中数据的共享是通过函数与函数之间的数据共享来实现的,其中包括两个途径:参数传递和全局变量。
- (1).静态数据成员:
- 如果某个属性为整个类所共有,不属于任何一个具体对象,则采用static关键字来声明一个静态成员。静态成员在每个类只有一个副本,由该类所有对象共同维护和使用,从而实现了同一类的不同对象之间的数据共享。类属性是描述类的所有对象共同特征的一个数据项,对于任何对象实例,它的属性值是相同的静态数据成员具有静态生存期。由于静态数据成员不属于任何一个对象,因此可以通过类名对它进行访问,一般用的语法是:
- 类名::标识符;
- (2).静态函数成员:
- 静态成员函数可以直接访问该类的静态数据和函数成员。而访问非静态成员,必须通过对象名。
- class A{
public:
static void f(A a);
private :
int x;
};
void A::f(A a){
cout<<x;// This is WRONG!
cout<<a.x;//之所以在静态成员函数中访问类的非静态成员需要指明对象是因为对静态成员函数的调用是没有目的对象的,因此不能像非静态成员函数那样隐含地通过目的对象访问类的非静态成员。
}
*
- 4.类的友元:
- 友元关系提供了不同类或对象的成员函数之间、类的成元函数与一般函数之间的关系进行数据共享的机制。通俗的说,就是一个类主动声明哪些其他类或函数是它的友员,今儿给它们提供对本类的访问特许。通过友元关系一个普通函数或者类的成员函数可以访问封装与另一个类中的数据。从一定程度上讲,与友元关系是对数据隐蔽和封装的破坏。
- 在一个类中可以利用关键字friend将其他函数或类生命为友元。如果友元是一般函数或类的成员函数,称为友元函数;如果友元是一个类,则称为友元类,友元类的所有函数都自动成为友元函数。
- (1).友元函数在类中用关键词friend修饰的非成员函数。友元函数可以使一个普通函数也可以是其他类的成员函数。
include "iostream"
include "cmath"
using namespace std;
class Point{
public:
Point(int x=0,int y=0):x(x),y(y){};
int getX(){return x;}
int getY(){return y;}
friend float dist(Point &p1,Point &p2);//友元函数声明;
private:
int x,y;
};
//友元函数dist的定义
float dist(Point &p1,Point &p2){
double x=p1.x-p2.x;
double y=p1.y-p2.y;
return static_cast<float>(sqrt(xx+yy));
}
int main(){
Point myP1(1,1),myP2(4,3);
cout<<"The distance is:";
cout<<dist(myP1,myP2)<<endl;
return 0;
}</float>- 在Point类中只声明友元函数的原型,友元函数dist的定义在类外,可以看出友元函数通过对象名直接访问了Point类的x和y属性。
- (2).友元类:
- 若类A为B类的友元类,则A类的所有成员函数都是B类的友元函数,都可以访问B类的私有和保护成员。
- class B{
- friend class A;//声明A为B的友元类。
- };
- 声明友元类是建立在类与类之间的练习,实现类与类之间数据的共享的一种途径。
include
using namespace std;
class A{
public:
void display(){cout<<x<<endl;}
int getX(){return x;}
friend class B;
private:
int x;
};
class B{
public :
void set(int i);
void display();
private:
A a;
};
void B::set(int i){
a.x=i;//因为B是A的友元,所以在B的成员函数中可以访问A类的所有私有成员;
}
*
- 注:友元关系是不能传递的,B是A的友元,C是B的友元,如果没有声明C是A的友元就没有友元关系。友元关系是单向的,如果B是A的友元,B可以访问A的私有数据和保护数据,但A的成员函数不能访问B的私有和保护数据。友元关系是不能被继承的,如果B是A的友元,B的派生类不能自动的成为A的友元。
- 5.共享数据的保护:
- (1).常对象:
- 常对象的数据成员值在对象的整个生存期内不能被改变。也就是说常对象必须进行初始化,而且不能被更新。
- const 类型说明符 对象名;
- class A{
public:
A(int i,int j):x(i),y(j){};
private :
int x,y;
};
const A a(3,4);//a是常对象,不能被更新。 - (2).const 修饰的类成员:
- 常用成员函数:使用const关键字修饰的函数为常成员函数,声明格式:
- 类型说明符 函数名(参数表) const;
- 如果将一个对象设置为常对象,则通过该常对象只能调用它的常成员函数,而不能调用其他成员函数;
- const关键字可以用于对重载函数的区分,void print(); void print() const;
- 常数据成员:使用const说明的数据成员为常数据成员,如果在一个类中说明了一个常数据成员,那么在任何函数中都不能对该常数据成员赋值。
include
using namespace std;
class A{
public:
A(int i);
void print();
private:
const int a;
static const int b;
};
const int A::b=10;//静态常数据成员在类外说明和初始化;
A::A(int i):a(i){}//常数据成员只能通过初始化列表来获取初值;
void A::print(){cout<<a<<":"<<b<<endl;}
int main(){
A a(100),a2(3);
a.print();
a2.print();
return 0;
}- 常引用:
- 如果在申明引用时用const修饰,被申明的引用就是常引用,常引用所引用的对象不能被更新。如果常引用作为形参,便不会发生对实参的更改。
- const 类型说明符 &引用名;
- 非const的引用只能绑定到普通的对象,而不能绑定到常对象,但常引用可以绑定到常对象。一个常引用,无论是绑定到一个普通对象还是常对象,通过该引用访问该对象时,只能把该对象当作常对象。这意味着对于基本数据类型的引用,则不能为数据赋值,对于类类型的引用,则不能修改它的数据成员,也不能调用它的非const的成员函数。
- 6.C++多文件结构和编译预处理命令:
- (1).C++的一般结构:
- 在多个文件结构中,#include指令的作用是将指定的文件嵌入到当前源文件中,这个被嵌入的文件可以使cpp文件,也可以是h文件。指令include有两种写法:#include<文件名>表示按照标准方式搜索要嵌入的文件,该文件位于编译器环境的include子目录下,一般嵌入系统提供的标准文件时采用的方式。另一种就是#include"文件名"表示首先在当前目录下搜索要嵌入 的文件,如果没有再按照标准方式搜索。
- (2).外部变量:
- 外部变量可以在源文件中可以使用,还可以被其他文件使用。 命名空间作用域中定义的变量,默认情况下都是外部变量,但在其他文件中如果需要使用这一变量,需要用extern关键字加以声明。
- (3).外部函数:
- 在所有类之外声明的函数(非成员函数),都是具有命名空间作用域的,如果没有特殊说明,这样的函数都可以在不同的编译单元中被调用,只要在调用之前进行引用性声明。也可以在声明函数原型或定义函数时用extern修饰。
- 7.标准C++库:
- C++的库中保留了大部分C语言系统函数和另外预定义的模板和类。使用标准C++库时,还需要加入下面一条语句来将指定命名空间中的名称引入到当前作用域中:
- using namespace std;
- 如果不使用using namespace std,就需要在使用std命名空间中的标识符时冠以命名空间名std::;
- (1).编译预处理:
include指令:
- 文件包含指令,起作用是将另一个源文件嵌入到当前源文件中该点处,通常用#include指令来嵌入头文件。
define和#undef指令:
define曾经在C程序中被广泛使用,但#define能完成的一些功能,能够被C++引入的一些语言特性很好的代替。在C语言中用#define来定义符号常量,如:#define PI 3.14;在C++中也同样定义符号常量,但是更好地方法是在类型说明语句中用const修饰。#undef的作用是删除由#define定义的宏,使之不再起作用。
- 条件编译指令:
if 常量表达式 或 #ifdef 标识符 或 #ifndef 标识符
- 程序段;
elif
- 程序段;
else
- 程序段;
endif
- */