当前课程知识点:C++语言程序设计基础 > 第5章 数据的共享与保护 > 共享数据的保护 > 共享数据的保护(例5-7)
共享数据的保护
l 对于既需要共享、又需要防止改变的数据应该声明为常类型(用const进行修饰)。
l 对于不改变对象状态的成员函数应该声明为常函数。
常类型
l常对象:必须进行初始化,不能被更新。
n const 类名 对象名
l常成员
n 用const进行修饰的类成员:常数据成员和常函数成员
l常引用:被引用的对象不能被更新。
n const 类型说明符 &引用名
l常数组:数组元素不能被更新(详见第6章)。
n 类型说明符 const 数组名[大小]...
l常指针:指向常量的指针(详见第6章)。
常对象
l用const修饰的对象
l例:
class A
{
public:
A(int i,int j) {x=i; y=j;}
...
private:
int x,y;
};
A const a(3,4); //a是常对象,不能被更新
l思考:哪些操作有试图改变常对象状态的危险?
常成员
l用const修饰的对象成员
l常成员函数
n 使用const关键字说明的函数。
n 常成员函数不更新对象的数据成员。
n 常成员函数说明格式:
类型说明符 函数名(参数表)const;
这里,const是函数类型的一个组成部分,因此在实现部分也要带const关键字。
n const关键字可以被用于参与对重载函数的区分
l通过常对象只能调用它的常成员函数。
l常数据成员
n 使用const说明的数据成员。
例5-7 常成员函数举例
#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);
a.print(); //调用void print()
const R b(20,52);
b.print(); //调用void print() const
return 0;
}
例5-8 常数据成员举例
#include <iostream>
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和b,并以100和0作为初值,分别调用构造函数,
//通过构造函数的初始化列表给对象的常数据成员赋初值
A a1(100), a2(0);
a1.print();
a2.print();
return 0;
}
常引用
l如果在声明引用时用const修饰,被声明的引用就是常引用。
l常引用所引用的对象不能被更新。
l如果用常引用做形参,便不会意外地发生对实参的更改。常引用的声明形式如下:
n const 类型说明符 &引用名;
例5-9 常引用作形参
#include <iostream>
#include <cmath>
using namespace std;
class Point { //Point类定义
public: //外部接口
Point(int x = 0, int y = 0)
: x(x), y(y) { }
int getX() { return x; }
int getY() { return y; }
friend float dist(const Point &p1,const Point &p2);
private: //私有数据成员
int x, y;
};
float dist(const Point &p1, const Point &p2) {
double x = p1.x - p2.x;
double y = p1.y - p2.y;
return static_cast<float>(sqrt(x*x+y*y));
}
int main() { //主函数
const Point myp1(1, 1), myp2(4, 5);
cout << "The distance is: ";
cout << dist(myp1, myp2) << endl;
return 0;
}
大家好
欢迎回来继续学习
C++语言程序设计
这一节我们来学习
共享数据的保护
如果我们既需要共享数据
又需要保证被共享的这些数据
不被修改
保证它的安全性
这个时候呢
我们就可以将这些数据
定义为常类型
也就是用const修饰的类型
那么在类的成员函数中呢
有一些函数
它是不会改变对象状态的
那么这样的函数呢
我们又可以把它定义成常函数
也就是
可以用const来修饰这个函数
首先我们来看看
我们可以在程序中
定义哪些常类型
我们可以定义常对象 也就是说在定义对象的时候呢
前面加这样一个const来修饰
常对象就像我们定义变量的时候
定义常量的时候一样
它必须在定义的时候
进行初始化
我们定义基本类型的常量的时候
不也是吗
必须在定义的时候初始化
定义常对象的时候也必须初始化
而且一旦初始化以后
就不能再被更新了
在类中
我们还可以把类的成员
定义成const成员 常成员
这个常成员又包括常数据成员
和常函数成员
另外呢
我们很多时候会传递引用
去作为函数的参数 对吧
我们也知道引用是可以达到
数据双向传递的
那这个时候
如果我们既要用引用
又不想被引用的对象被更新
不想达到双向传递
那么怎么办呢
可以将参数定义为常引用
也就是只读的引用
另外在第六章
我们还会学习常数组
以及常指针
接下来我们首先来看看
如何定义一个常对象
以及常对象它有什么样的特点
那常对象呢
就是用const修饰的对象
比如说我们这样有一个a类
定义好了
接下来
我们要去定义a类的对象的时候
我们在普通的对象
定义的这种语法形式里面
加一个const
就说这个对象是常对象了
常对象它的特点是什么
就是不能再被更新了呀
不能被改变了呀
那我们再想一个问题
编译器做语法检查的时候
怎么能够保证你这个常对象
是不被改变的呢
好 我们把这个问题留着
后面会给大家介绍
专门用来处理常对象的函数
叫常函数
那么对象的成员
实际上也可以是const成员
在定义这些成员的时候
都可以加const修饰
把它定义成常成员
那常成员又分成常数据成员
和常成员函数
常成员函数就是我们刚才说了
专门用来处理常对象的
它是用const来修饰的
这样的函数
相当于它做出了承诺
它承诺说我绝不改变对象的状态
所以在这样的函数原型的最后
要加const
那么const也是重载函数的一个
区分的因素
那么常数据成员
就是用const修饰的数据成员
下面我们来看一个例题
体会一下常成员函数
它是有什么特点的
是用来干什么用的
在这个例题中呢
我们使用了常成员函数
大家看这个R类里面呢
定义了两个print函数
其中一个呢后面有const
是个常函数
const也是区分
不同重载形式的因素之一
在此之前我们只学过
要区分不同的重载函数
就要用不同的参数表
那么看这两个print函数
它的参数表都是一样的
都是空的
但是其中一个后缀了const
也是合法的重载形式
所以const也是区分
重载函数的一个因素
现在我们来看这个
print函数的实现
我们看到这两个print函数
它的函数体实现
基本上是一样的
但是
这个有const后缀的这个函数
它就特别地承诺了
这个函数它绝不改变对象的状态
那有同学可能觉得
我这第一个print函数
也并没有改变对象的状态
它只是输出R1 R2
对于状态没有什么改变
那么这个呢
必须要显式地标出来是const
编译器在编译的时候
才会去认真审查你这个函数体
看还有没有改变状态的语句
如果在其中有改变
对象状态的语句
它就会报语法错
但是如果你不后缀const
编译器在编译的时候
就不会去审查这一点
所以就不能保证这个函数
它不改变对象状态
我们有了这样的常函数
目的是什么呢
目的是为了处理常对象
我们知道一个对象
如果它定义的时候
定义为const
就说明这个对象
它是一个常量
一旦在定义的时候被初始化了
它就不能再被修改了
而修改对象的状态
有多少种办法呢 很多啊
你调用它每一个函数
都有可能修改对象的状态
都有这个危险性
不像基本类型的变量
你要修改它的状态
一个是给它赋值
一个是从键盘输入数据装进去
大概也就是这样的途径吧
但是对象呢
它有那么多的成员函数
我们这里头举例举的比较简单
其实可以罗列很多成员函数的
它对每个成员函数的运行
都有可能改变它的状态
那么编译器怎么控制这一点呢
就用常函数
也就是说
像这样定义的一个常对象
你只能用常函数去处理它
不是常函数
不能通过常对象调用
我们看这儿通过b
调用同一个函数b.print
那么调用的是谁呢
调用的就是
有const修饰的这个print
而a.print呢
调用的就是
没有const修饰的这个print
那么现在呢
我们考虑一个问题
如果这个print函数
反正它是不需要改变对象状态的
那么我也就没有必要
写两个版本了 对吧
我只要有一个常函数print就行了
不带const的这个print
我不要了行不行呢
希望大家课后去试一试
应该是可以的
如果你这个类里面
只有一个常函数
没有这个第一个print函数
那么a.print调用谁呢
它也去调用常函数
一个普通对象
调用常函数是没有任何问题的
所以通过这个例题呢
实际上我们得到了一种经验
在一个类中
如果你定义一个函数的时候
发现这个函数
就类似于print这样的函数
它的本意就是不打算改变
对象的状态
反正它是不会改变的
那么你就加一个const
加一个const
表示一种保证 一种承诺
承诺我不改变对象的状态
我可以用来处理常对象
所以如果能将一个类里面
不打算改变对象状态的函数
主动都说明为常函数
那么以后定义这个类
常对象的时候
使用起来就会比较方便
定义类数据成员的时候呢
我们也可以用const来修饰
那么接下来呢
我们再来看一个
类里面有const数据成员
也就是常数据成员的例子
我们看在这个例子中呢
我们定义了两个常数据成员
看看这个数据成员变量a
前面有const
这个a是属于每个对象的成员
再看这个数据成员b
它是一个静态成员
是属于整个类的
也定义为常成员
所以对象的成员 类的成员
都可以定义为常成员
一旦一个成员定义为常成员了
那么就只能在初始化的时候
给它初始值
不可能
也不允许在别的地方
再给它赋值了
我们看怎么来做初始化的
我们来看怎么做初始化的
首先看这个静态成员b
它在类体中声明
在类体外定义和初始化
所以它就需要在这儿进行初始化
初始化完了以后
就再也不许改变了
因为它是常量
再看这个对象的成员a
它是在构造每一个对象的时候
完成初始化的
比如我们来看这儿
构造对象a1的时候
给一个100作为初始值
然后这个时候会调用构造函数
我们看
在这个构造函数的初始化列表中
就用参数i去初始化了这个
常数据成员a
那么如果是其他的成员
如果不是常成员的话
我们知道
既可以在初始化列表中
完成初始化
也可以在函数体中进行赋值
至于有的成员呢
它不是一个简单的赋值
就可以初始化的
还要经过一些计算
才能得到初始值
那么在函数提中
可能它要发生一些计算
最后将计算结果
复给这个成员变量
但是这个常成员
它是不可以放在
构造函数的函数体中赋值的
它必须在初始化列表中
进行初始化
以后就不许再赋值了
像a2也是
a2用0作为参数去构造它
那么0传过来
就传到i里面
也是在初始化列表里面
它初始化的成员变量a里面了
接下来我们输出对象a1 a2的
它的数据成员a b
我们看到a1的数据成员a是100
b1的成员a是b.a 是0
它们的b成员都是10
因为这个b是一个静态成员
为所有的对象所共享的
那么什么是常引用呢
常引用也是用const修饰的引用
这样的引用相当于是个只读的
只能用它
去读取它指向对象的内容
但是不能去修改它指向的对象
下面我们来看一个
用常引用做参数的例子
用引用做参数
一方面
它可以实现双向的数据传递
另一方面
它传递参数的效率比较高
那么如果我们仅仅需要
这个高效率的特点
而不希望进行双向传递
这个时候我们就可以这个引用
加一个限制
让它是常引用
它也就是只读的引用了
下面我们在这个例题中
给大家演示
大家还记得在前面
友元函数的那个例子中
遗留的那个问题吗
当时我们用引用做函数的参数
这样能够提高参数传递的效率
但是也存在着一个隐患
就是说引用
它是可以双向传递数据的
用引用做参数呢
如果在函数中
对参数做了任何修改
就会影响到实参
现在我们就来解决这个问题
大家看仍然是以这个点类为例
仍然是在点类中
声明了一个友元函数dist
友元函数的函数体
跟我们上一个例子是一样的
计算两个点的距离
只不过呢
我们来看参数表
有了少许变化
在这两个参数前面都加了const
说明这两个参数都是常引用
常引用是什么意思呢
就是只读的引用
也就是说通过这个引用
只能读取
它所指向的对象里面的值
不能通过这个引用
去修改它指向的对象
具体到这个例子中呢
就是说通过引用p1
可以去取得p1.x的值 p1.y的值
但是你不能修改p1.x
不能修改p1.y
那p2也是一样
可以读取p2.x的值拿来用
读取p2.y的值拿来用
拿来参与运算
但是你都不能去修改它
那大家回去可以改一改这个题目
你看看编译器会不会给你报错
你就试图在这个dist函数里面
给p1.x重新赋值
或者给p2.y重新赋值
看看编译器会不会给你报错
肯定是会报错的
大家可以验证一下
这样你的印象就比较深刻了
好 那计算还是一样的
这样我们就可以比较放心地
用这个友元函数了
既提高了访问效率
让它能够直接访问私有成员
又不会因此对私有成员的安全性
有任何破坏
-导学
--第1章导学
-计算机系统简介
--计算机系统简介
--计算机系统简介 测试题
-计算机语言和程序设计方法的发展
--计算机语言和程序设计方法的发展 测试题
-面向对象的基本概念
--面向对象的基本概念 测试题
-程序的开发过程
--程序的开发过程
--程序的开发过程 测试题
-信息的表示和储存
--计算机的数字系统
--数据的编码表示
--信息的表示和储存 测试题
-实验指导
-导学
--第二章导学
-C++语言概述
--C++语言概述 测试题
-基本数据类型、常量、变量
--程序举例
--基本数据类型、常量、变量 测试题
-运算与表达式
--运算与表达式 测试题
-实验二:简单程序设计(上)
-数据的输入和输出
--数据的输入和输出
--数据的输入和输出 测试题
-选择结构
--if语句
--switch语句
--选择结构 测试题
-循环结构
--for语句
--循环结构 测试题
-自定义类型
--自定义类型
--自定义类型
-第2章小结
--第二章小结
-实验二:C++简单程序设计(下)
-导学
--导学
-函数定义
--函数定义
--函数定义 测试题
-函数调用
--例3-2
--例3-3
--例3-4
--例3-5
--例3-6
--函数调用 测试题
-嵌套与递归
--例3-9
--例3-10
--嵌套与递归 测试题
-函数的参数传递
--函数的参数传递
--函数的参数传递 测试题
-引用类型
--引用类型 测试题
-含有可变参数的函数
--含有可变参数的函数 测试题
-内联函数
--内联函数 测试题
-constexpr函数
--CONSTEXPR函数课后习题
-带默认参数值的函数
--带默认参数值的函数 测试题
-函数重载
--函数重载 测试题
-C++系统函数
--C++系统函数习题
-第3章小结
--第三章小结
-实验三(上)函数的应用
-实验三(下)函数的应用
-导学
--导学
-面向对象程序的基本特点
--面向对象程序的基本特点 测试题
-类和对象
--类和对象的定义
--类和对象 测试题
-构造函数
--构造函数基本概念
--委托构造函数
--复制构造函数
--构造函数 测试题
-析构函数
--析构函数
--析构函数 测试题
-类的组合
--类的组合
--类的组合程序举例
--前向引用声明
--类的组合 测试题
-UML简介
--UML简介
--UML简介课后习题
-结构体与联合体
--结构体与联合体 测试题
-枚举类
--枚举类
--枚举类 测试题
-第4章小结
--第四章小结
-实验四(上)
--实验四(上)
-实验四(下)
--实验四(下)
-导学
--导学
-标识符的作用域与可见性
--标识符的作用域与可见性 测试题
-对象的生存期
--对象的生存期
--对象的生存期 测试题
-类的静态成员
--类的静态成员 测试题
-类的友元
--类的友元 测试题
-共享数据的保护
--共享数据的保护 测试题
-多文件结构和预编译命令
--多文件结构和预编译命令 测试题
-第5章小结
--小结
-实验五
--实验五
-导学
--导学
-数组的定义与初始化
--数组的定义与使用
--一维数组应用举例
--数组的定义与初始化 测试题
-数组作为函数的参数
--数组作为函数的参数 测试题
-对象数组
--对象数组
--对象数组 测试题
-基于范围的for循环
-指针的定义和运算
--指针的定义和运算 测试题
-综合实例
--综合实例
-实验六(上)
--实验六上
-指针与数组
--指针数组
--指针与数组 测试题
-指针与函数
--指针类型的函数
--指向函数的指针
--指针与函数 测试题
-对象指针
--对象指针
--对象指针 测试题
-动态内存分配
--动态内存分配 测试题
-智能指针
--智能指针
-vector对象
--vector对象
--vector对象 测试题
-对象复制与移动
--移动构造
--对象复制与移动 测试题
-字符串
--C风格字符串
--string类
--字符串 测试题
-第6章小结
--第六章小结
-综合实例
--综合实例
-实验六(下)
--实验六(下)