参考书籍:《c++程序设计》张军编著 第七章,实际为笔记,哈哈
本篇文章主要讲了一些为了简化编码,节约内存,数据安全性 时,类与对象所涉及到的一些技术。并有程序加以运行验证,大多来自参考数据,自己有所添加。
1.this 指针
我们知道,class类有有函数,然后实例化的对象会对应的函数(实际上相当于调用了函数)。
比如上文提到的Clock myClock1,myClock2,各自独立存放在内存的数据区,每个内存单元互不干涉。而成员函数在代码区只有一个靠背,并且函数存放在代码区里,也就是说在物理内存上,对象的数据和处理对象的函数是分离的。
既然函数的位置是只有一个,不同对象调用函数是怎么知道其位置的呢?
为了正确找到对象的地址,系统会自动产生一个指针(tihs指针)指向当前对象。
比如调用成员函数:myClock2.setShow(); 实际上是这样的:setShow(&myClock2);
将对象myClock2的地址传给形参this指针,然后通过this指针找到相应的对象。
但是,这些系统自己都会做啊,我们学this指针有什么用呢?
问题是:当形参名与数据成员名相同时,我们为了区别,这里必须使用this指针。
比如:
void Clock:;setTime(int hour, int minute,int second)
{
this->hour=hour;
this->minute=minute;
this->second=second;
}
附录:
C++的使用中我们经常会用到.和::和:和->,在此整理一下这些常用符号的区别。
- A.B则A为对象或者结构体;
- A->B则A为指针,->是成员提取,A->B是提取A中的成员B,A只能是指向类、结构、联合的指针;
- ::是作用域运算符,A::B表示作用域A中的名称B,A可以是名字空间、类、结构;
- :一般用来表示继承;
2.类的静态成员
全局变量在程序的整个运行期间都要占用内存空间,并带来数据安全性问题。
所以我们想节约空间,就会减少使用全局变量,比如把它写在类里面。
比如我们建个学生类,
class Student
{
public:
int num; // 学生编号
int count; //统计学生人数
}
Student student1;
c++里查看内存空间占用字节多少,是用sizeof()函数,
可是我们这样有个问题;
count是和学生这个类有关的量,一个对象决定不了这个值,它和对象们有关。
如果我们这样来存储 count,很明显 sizeof(student1)=8,对象一多,就会很占用内存。
这时候,静态成员 横空出世
class Student
{
public:
int num; // 学生编号
static int count; //统计学生人数
}
int Stident::count=0; //静态成员必须进行初始化,且只能在类外初始化
//初始化时,不能加上static,比如static int Stident::count=0 会报错的。
这时候,这个类初始化的时候,会给静态变量分配内存,然后每个对象的大小为4字节。
假如对象为n个,那么节省的空间为4(n-1)字节,(这里4,因为int count大小为4字节)
#include<iostream>
#include<string>
using namespace std;
class Student
{
public:
Student()
{
name = "";
id = 0;
count++;
}
~Student()
{
count--;
}
static int getCount()
{
return count;
}
private:
string name;
int id;
static int count;
};
int Student::count = 0;
void main()
{
Student stu1;
cout << "count=" << Student::getCount() << endl;
Student *ps = new Student();
ps = new Student[100];
cout << "count=" << Student::getCount() << endl;
delete[] ps; //释放动态数组,调用了100次析构函数
cout << "count=" << Student::getCount() << endl;
system("pause");
}
(此处为了封装性,把函数getCount() 封装起来,同样为了可以调用,static。)
静态成员函数有两种调用方式,
className::staticfunction(ArgumentList) ,即Student::getCount()
或者 object.staticfunction(ArgumentList) ,即Student1.getCount(),哈哈如果Student1没被删除的话
静态成员函数不能访问非静态的数据成员;
但是,一个非静态的成员函数,既可以访问静态的数据成员,也可以访问非静态的数据成员。
一般的,将工具类成员函数设置为static.
比如,我们建立一个类Math.
#include<iostream>
#include<string>
using namespace std;
class Math
{
public:
static int addValue(int a, int b)
{
return (a + b);
}
};
void main()
{
int c,d,f;
c = 1, d = 2;
f = Math::addValue(c, d);
cout << f<<endl;
system("pause");
};
3.数据的保护
正如我们所知,我们可以用const定义常量,比如const int g=1;这样的话,g的值就不能被修改了。
基于const的这个特点,我们可以实现数据保护,即数据虽然被引用了,却没有被随意改变或者不小心被改变了。
const int gg=1;
//gg不能改变
int g = 1;
int &ra =g;//ra引用g,或者说ra是g的别名
cout << ra << endl;
ra++;//ra改变,g也改变,这样就实现不了数据的保护了
cout << g << endl;
//------------------------------//
const int &la = g;
//la++; //不能通过常引用 改变所引用对象的值
cout << g << endl;
参数保护完毕后,那就到函数了。
常函数成员只能调用常函数成员,不能调用非const函数成员;
而普通函数成员则可以调用常函数成员与非常函数成员。
常函数成员的声明: datatype functionname(datatype1 var1) const;
定义:datatype className::functionName()datatype1 var1) const
{
} //此处const不能省略
性质:
常对象只能调用常函数成员,不能调用非const函数成员;
const关键字可以用于函数的重载,非成员函数定义时不能加const关键词。
#exmaple 3
#include<iostream>
#include<string>
using namespace std;
//函数:定义一个point类,并计算其距离。计算距离的函数不应改变输入实参的值,这样就需要引用。
class Point
{
public:
Point() //构造函数;
{
x = 0, y = 0;
}
Point(int x, int y)//带参数的构造函数,重载
{
this->x = x;
this->y = y;
}
int GetX() const;
int GetY() const; //生命为常成员函数;
private:
int x;
int y;
};
int Point::GetX() const
{
return x;
}
int Point::GetY() const
{
return y;
}
double Distance(const Point &a, const Point &b)//形参定义成常引用
{
int dx = a.GetX() - b.GetX();
int dy = a.GetX() - b.GetY();
return sqrt(dx*dx + dy*dy);
}
void main()
{
Point A;
Point B(2, 3);
Point C(5, 6);
cout << "distance:" << Distance(A, B) << endl;
system("pause");
};
4.类的友元
从上面example3的代码可以发现,在保护数据安全性的要求下,我们private数据,用函数的形式GetX()调用它。然而,这样无疑是繁琐,不方便的。
在这样的背景下,抱着“懒一点”的想法,友元 函数横空出世。
举个例子说,对一个人的家里而言,卧室是private的,客厅是public的,但偶尔需要对隔壁老王开放一下卧室,那就让老王获得friend权限,就可以以数据而不是函数的形式了。
我们知道类的数据private时,对类自身的函数是开放的,对你的爱人还是开放的。
需要这个friend权限的,就是两种人:1,同样有家的人(另外一个类的成员函数);2,无家可归的人(普通函数)
先来看看无家可归的人,他想当朋友就比较简单,毕竟都知道不在隔壁,是吧。here we go!
声明方式:friend Datatype Functionname(datatype1 var1..);
定义方式: Datatype Functionname(datatype1 var1,..) //定义时,不要加friend了;
性质:1. 首先我们不能忘记一点,友元函数不是类的成员函数,谁会给自己的家人盖一个“朋友”许可证呢?
所以不能object.friendFunction()这样使用。
2.友元函数放在public和private都可以,反正都要领朋友证了。
#exmaple 4 展示了无家可归的朋友~!
#include<iostream>
#include<string>
using namespace std;
//函数:定义一个point类,并计算其距离。计算距离的函数不应改变输入实参的值,这样就需要引用。
class Point
{
public:
Point() //构造函数;
{
x = 0, y = 0;
}
Point(int x, int y)//带参数的构造函数,重载
{
this->x = x;
this->y = y;
}
friend double Distance(const Point &a, const Point &b)//形参定义成常引用
private:
int x;
int y;
};
double Distance(const Point &a, const Point &b)//形参定义成常引用
{
int dx = a.x - b.y;
int dy = a.x - b.y;
return sqrt(dx*dx + dy*dy);
}
void main()
{
Point A;
Point B(2, 3);
Point C(5, 6);
cout << "distance:" << Distance(A, B) << endl;
system("pause");
};
我们再来看看其他家庭的要获得朋友许可证,怎么走流程呢?首先获取“朋友”证书的地方肯定是在主人家里塞。
class A
{
public :
void fa1(); //A的成员fa1()函数 的声明
private:
int x;
}
class B
{
public:
friend void A::fa1(); //在B的家里,给A的成员fa1()函数 颁了“朋友”证书
private:
int y;
}
void A::fa1()
{ B b;
b.y=1;
} //A的成员fa1()函数 的定义
发现没有,这两种都是一样的:1.都在主任家里获取“朋友”证书,2.只是有家的人需要 把自己来自的类说清楚,
即friend classname::functionname(datatype1 var1,...)的形式。
如果这个家的人想给另一家的所有人都颁发“朋友”证书呢,一个一个的授权岂不很麻烦,懒人又来了,哈哈:
class B; //提前引用声明
class A
{
friend B;
}
class B
{
friend A;
}
//记住,朋友的朋友不一定是朋友,要是朋友,就得到主人家去认识认证。即友元关系 --非传递性;
//友元关系式单向的,我是你朋友,你不一定是我朋友
5. 类模板
模板temple可以解决功能相同而数据类型不同的函数重复写的情况。
话虽这么说,但是好像 有时不用也行呢?如下:数据类型不同时,
#include<iostream>
#include<string>
using namespace std;
void main()
{
int a = 3;
float b = 2.9;
float c = (a > b) ? a : b;
cout << c << endl;
system("pause"); //不同类型的数据之间仍然可以比较,应该是转化成精度最高的那种数据进行比较;
//也就是说,很多情况下,数据类型不同,仍然可以进行计算,数据会自动转换的
}
对于函数而言,我们需要注意:当实参与形参不一致,应该是以形参为准。在调用函数时,编译器只知道形参类型,没法知道实参类型。就是说,用函数比较大小,他也是会进行转换的。但是,转换数据类型有时并不好。
回到这里,temple的重大作用,在跨平台领域(不同平台,所具有的数据类型可能不同,或者命名方式不同)特别有用,只用轻轻一改,就适合多种不同数据类型。这里是一个函数模板的示例,和类模板的唯一区别,这里用typenanme,类模板用class。
#include<iostream>
#include<string>
using namespace std;
//函数模板的例子
template<typename T>
void swap(T &t1, T &t2)
{
T tmpT;
tmpT = t1;
t1 = t2;
t2 = tmpT;
}
void main()
{
int a=3;
int b=4
swap<int>(a,b);
}
既然是类模板,先建立类(具有模板性质的类,所以不叫声明与定义,就描述一个抽象的类),然后再实例化它。
语法形式:
template<class T1,class T2,...>
class className
{
member declaration;} //本质上就是在原来类的基础上加了这么半句(没有分号哦,和下文衔接的)template<class T1,class T2,...>
实例化:className<T1,T2> objectname;
,