C++作为编写大型复杂程序的语言,数据的共享与保护沪机制是C++的重要机制之一。
本文主要归纳了以下几个:
1)标识符的作用据与见性
2)对象的生存期
3)共享数据
4)保护数据
一:标识符
(1):作用域
作用域是一个标识符在程序中的有效区域;
局部作用域:
如一个函数的形参,其作为标识符的作用域在这个函数体的开始到结束;
命名空间的作用域:
命名空间作为我们最熟悉的语句是
using namespace std;
命名空间作为一块内存空间的集合,里面定义了许多标识符(函数),如cout、cin;
定义方式为
namespace std{
....
}
其内标识符的作用域为这个命名空间{ 的开始到 }结束、一个命名空间确定了一个命名空间的作用域
当我们想在这个作用域之外想访问引用该命名空间的表示符:
1)当程序不写using namespace std,而想使用cout函数
std::cout<<“。。。”<<std::endl;
2)当想在程序里自由使用std空间内的表示符
using namespace std;
3)当只想在程序里自由使用std里的特定的标识符
using std::cout;
后二种
using 命名空间名::标识符;
是将该标识符的作用域扩充到当前作用域;
using namespace 命名空间名;
是将该命名空间作用域扩充到当前作用域;
(2)可见性
程序运行到某一点,能够引用到该标识符,就是该处空间的可见标识符
1)引用
引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。
引用的方法
类型标识符 &引用名=目标变量名;
比如:
int a; //内存分配了四个字节空间给a,a代表了这四个字节的空间
int &b=a; //a的这四个字节空间,又有另外一个别名为b
b=1;
cout<<a<<endl; //此时a的值也为1
引用作为参数
引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为 这样可以避免将整块数据全 部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引 用。
#include<iostream>
using namespace std;
void swap(int &m,int &n) //&m=a,&n=b
{
int c;
c=m;
m=n;
n=c;
}
int main()
{
int a=1,b=2;
swap(a,b);
cout<<a<<" "<<b<<endl;
}
等价于
#include<iostream>
using namespace std;
void swap(int *a,int *b)
{
int c;
c=*a;
*a=*b;
*b=c;
}
int main()
{
int a=1,b=2;
int *p=&a,*p1=&b;
swap(p,p1);
cout<<a<<" "<<b<<endl;
}
可以看出
1 传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
2 使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给 形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函 数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效 率和所占空间都好。
3 使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的 形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须 用变量的地址作为实参。而引用更容易使用,更清晰。
当我们想使用引用,又不想引用改变原变量的值时,应使用常引用
格式
const 类型标识符 &引用名=目标变量名;
int a;
int &b=a;
a=1; //正确
b=1; //错误
引用作为函数的返回值
1 以引用返回函数值,定义函数时需要在函数名前加&
2 用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。
我们知道,一般的函数返回值为临时变量,在内存中会临时建一个副本,当他有人接收时,该临时变量的内存将被命名为接收者的名称,当没有人接收时,过了该语句就被释放。
2)作用域可见性的一般规则
1 标识符声明在前,引用在后
2 在同一作用域中,不能声明同名的标识符
3 在没有包含关系的不同作用域的同名标识符互不影响
4 如果在二个或多个具有包含关系的作用域中声明了同名标识符,则外层标识符在内存看不见,比如类成员的隐藏规则
2)对象的生存期
对象(包括简单的变量)从诞生到被释放的这段时间就是它的生存期。但生存期不代表可见性,局部函数的静态变量,其可见性为该变量声明点到该块结束时。
1.静态生存期
如果对象的生存期一直能到程序运行结束,则称它具有静态生存期。
1 在命名空间中声明的对象,如果该空间在全局里被引用,则里面的对象都具备静态生存期
using namespce std;2 全局变量具有静态生存期
3 局部变量在定义前加static前缀,则该变量具备静态生存期
static int i;4 类数据成员在定义前加static前缀,则该成员具备静态生存期
类的静态数据成员存放在静态区,所有该类对象共享一个该成员内存。
类的静态数据成员访问,一般建议用类名::标识符。当然也可以用对象名.标识符。
类的静态数据成员的初始化须在类外初始化,不能在构造函数中初始化。
class A{ private: static int a; }; int A::a=1; //静态函数的定义和初始化5 类的函数成员在定义前加static前缀,则该成员具备静态生存期
类的静态函数成员的调用可以直接通过类::函数名,而非静态函数成员则只能通过对象名.函数。
类的静态函数成员里可以直接访问该类的静态数据成员和函数,而访问非静态的必须通过对象名
class A{ private: static int a; //静态数据成员 int b; //非静态数据成员 public: static void show(A m){ //静态函数成员 cout<<a<<endl; cout<<m.b<<endl; } }; int A::a=1; int main() { A m; m.show(m); A::show(m); }
2)动态生存期
动态生存期的对象成为局部生存期对象,诞生于声明点,结束于声明所在块的执行完毕之时 。
(3)共享数据
我们知道类的成员可以划分为公共成员、私有成员、保护成员和不可访问成员。
公有成员,类内外都可以直接访问。
私有成员、保护成员,只有类内可以直接访问。
不可访问成员由继承而来,类内外都不可以直接访问
所以有人把类形容成一个黑箱,外部只能通过各个接口使用类
通过类的数据隐藏保证了数据的安全性,但也有一些形式的数据共享可以破坏这种数据的安全性。
1)类的友元
类的封装不是绝对的,通过友元关系可以访问封装于另一个类里的成员。
友元函数
使类外定义的函数可以访问类内的所有成员;
#include<iostream>
using namespace std;
class A{
private:
int a;
public:
A(){
a=1;
}
friend void show(A &m); //声明show函数为A类的友元函数
};
void show(A &m) //友元函数可以直接访问A类对象的私有成员
{
cout<<m.a<<endl;
}
int main()
{
A m;
show(m);
}
友元类
若A类为B类的友元类,则A类可以直接访问B类的对象里的所有成员;
#include<iostream>
using namespace std;
class A{
private:
int a;
public:
A(){ //A的构造函数
cout<<"A的构造函数"<<endl;
a=1;
}
~A(){ //A的析构函数
cout<<"A a="<<a<<endl;
}
friend class B; //B是A的友元类
};
class B{
public:
B(A &n){ //B的构造函数,可以直接访问A类的对象n的所有成员
cout<<"B begin a="<<n.a<<endl;
n.a=2;
}
};
int main()
{
A n;
B m(n);
}
结果
A的构造函数
B begin a=1
A a=2
请按任意键继续. . .
关于友元的注意事项:
1 友元关系不能传递
B类是A类的友元,C类是B的友元,C和A之间没有友元关系
2 友元是单向的
B类是A类的友元,B可以直接访问A类对象的所有成员,但A类不能直接访问B类的所有成员
3 友元关系不能被继承
B类是A 类的友元,但B的派生类不是A类的友元
2) 虚函数表
类的虚函数表在内存中是一块连续的内存,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。
在基类的类定义中定义虚函数的一般形式:
virtual 函数返回值类型 虚函数名(形参表){
函数体
}
虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函数。
在定义了虚函数后,可以在基类的派生类中对虚函数重新定义。
#include<iostream>
using namespace std;
class A{
public:
virtual void fun(){ //虚函数表
cout<<"hello"<<endl;
}
void kun(){ //普通函数
cout<<"1"<<endl;
}
};
class B:public A{
private:
virtual void fun(){ //虚函数表
cout<<"world"<<endl;
}
void kun(){ //普通函数
cout<<"2"<<endl;
}
};
int main()
{
A *a;
A m;
B n;
a=&m;
a->fun();
a->kun();
a=&n;
a->fun();
a->kun();
return 0;
}
结果
hello
1
world
1
我们知道一般父类指针指向子类对象,指针值可以访问子类对象里继承于父类的成员。
但虚函数表突破了这个限制,不但可以访问子类的成员,而且该成员还在私有成员里。
为什么呢?
这里涉及到多态的性质。我们知道编译是静态的,运行是动态的。
当A类的指针a指向fun()时,因为fun()函数在A中是public:属性的,所以编译时默认为合法的。
但fun函数是虚函数,虚函数表在内存中是一块连续的空间,类内的虚函数可以在其派生类里重新定义。
运行时A类内的fun()函数已经被B类内的fun函数重新定义,所以访问到的是B类的fun函数。
(4)保护数据
上面我提到了一些形式可以破坏数据的安全性。下面我列举一些保护数据的方法。
1)常对象
常对象的数据成员值在其生存期内不能被改变。
因此它要初始化。
并且常对象不能调用非常成员函数。只能调用静态函数和常成员函常数。
声明常对象的语法形式:
const 类型说明符 对象名
比如:
class A{
public:
int a1;
A(){
a1=1;
a2=2;
}
private:
int a2;
};
int main()
{
const A m;
}
2)常成员函数
类内的常函数成员不能改变该类内数据成员。
常函数成员的声明格式
类型说明符 函数名(参数表) const ;
比如
#include<iostream>
using namespace std;
class A{
public:
void show(int &n) const{
n=1;
}
private:
int a;
};
void main()
{
int n;
A m;
m.show(n);
}
常函数的重载
const可以对重载函数进行区分
假设类内有二个同名公有函数
void fun();
void fun() const;
常对象.fun();调用的是常函数成员。第二个fun()。
非常对象.fun();调用的是非常函数成员。第一个fun()。
3)常数据成员
常数据成员只能初始化时对该成员赋值,其他时候不能赋值。
以下是三种常数据成员初始化方法
#include<iostream>
using namespace std;
class A{
private:
static const int a=1; //只有静态常量可以这么初始化
static const int b;
const int c;
public:
A():c(3) { //只有构造函数可以初始化常量成员,其他函数不行
cout<<a<<endl<<b<<endl<<c<<endl;
}
};
const int A::b=2;
int main()
{
A m;
}
结果
1
2
3
请按任意键继续. . .