C++学习:类与对象(二)

本文探讨了C++中的类与对象的相关技术,包括this指针的作用,类的静态成员如何节省内存,数据保护的重要性及常量成员函数,类的友元功能以及类模板的应用。通过实例解析了如何利用这些特性进行更有效的编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考书籍:《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++的使用中我们经常会用到.和::和:和->,在此整理一下这些常用符号的区别。

  1. A.B则A为对象或者结构体;
  2. A->B则A为指针,->是成员提取,A->B是提取A中的成员B,A只能是指向类、结构、联合的指针;
  3. ::是作用域运算符,A::B表示作用域A中的名称B,A可以是名字空间、类、结构;
  4. :一般用来表示继承;

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;

 

,

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值