C++学习笔记 类和对象(二)

本文详细介绍了C++中的面向对象特性,包括自引用指针(this指针)的作用,对象数组和对象指针的使用,堆对象的动态分配,以及如何通过对象、对象指针和对象引用向函数传递对象。此外,还探讨了静态成员(静态数据成员和静态成员函数)的功能,友元(友元函数和友元类)的特性,以及对象成员的初始化和常对象的概念。通过对实例的分析,展示了这些概念在实际编程中的应用。
摘要由CSDN通过智能技术生成

1. 自引用指针

  • 对象的自身引用时面向对象程序设计语言中特有的,十分钟重要的一种机制,C++中设立了专门的表示: this指针白能量
  • 在类的每一个成员函数的形参表中都有一个隐含的指针变量this,该指针变量的类型就是成员函数所属的类型
  • 当程序中调用类的成员函数时,this指针变量被自动初始化为发出函数调用的对象的地址
  • 成员函数中隐含着一个指针this,它指向调用成员函数的对象,在成员函数中可以直接使用该指针,如下图代码所示
#include <iostream>
using namespace std;
class Sample
{
	int x, y;
public:
	Sample(int a=0, int b=0)
	{ x=a; y=b; }
	void print()
	{ cout<<x<<endl;
	 cout<<y<<endl;
	}
};
int main()
{ Sample obj(5,10);
  obj.print();
  return 0;
}
上述程序可用this指针显示改写为完全等价的形式:
 #include <iostream>
using namespace std;
class Sample
{
	int x, y;
public:
	Sample(int a=0, int b=0)
	{ this->x=a;                //在此例中this=&obj
this->y=b; }
	void print()
	{ cout<<this->x<<endl;     //在此例中this=&obj
	 cout<<this->y<<endl;
	}
};
int main()
{ Sample obj(5,10);
 obj.print();
 return 0;
}
  • this的使用情况:
1. 为了区分成员和非成员。
void Sample::fun(int x)
{
  this->x=x;
 }
2. 一个类的方法需要返回当前对象的引用。
class Sample
{
 private:
 	int x;
	char* ptr;
 public:
	Sample & Set(int i, char *p);
    //..
};
Sample& Sample::Set(int i, char *p);
{
 x=i;
 ptr=p;
 return *this;
}

2. 对象数组和对象指针

(一) 对象数组

  • 每一个数组元素都是对象的数组
  • 对象数组的元素是对象,不仅由数据成员,而且有成员函数.定义对象数组时,系统为每个数组元素对象调用一次构造函数以构造这些元素.
  • 对象数组的定义格式: ; 类名 数组名[数组大小];
#include <iostream>
using namespace std;
class Sample
{
private:
int x;
public:
void Set_x(int n)
{ x=n; }
int Get_x()
{  return x;  }
};
int main()
{
Sample obj[4];
int i;
for(i=0; i<4; i++)
obj[i].Set_x(i);
for(i=0; i<4; i++)
cout<<obj[i].Get_x()<<” ”;
cout<<endl;
return 0;
}

3. 堆对象

  • 使用new运算符动态分配的对象属于堆对象,其所占存储空间被分配在堆区

4. 对象指针

  • 指向类对象的指针称为对象指针
  • 声明指向类对象的指针变量的一般形式: 类名 *对象指针名;
    • 指针变量名->成员名
    • (*指针变量名).成员名
  • 可以通过对象指针访问对象和对象的成员

5. 向函数传递对象

5.1 使用对象作为函数参数

  • 对象可以作为函数的值形式参数,调用函数时用同类的实际参数对象与之对应
  • 参数传递方法与传递其他类型的数据相同,是单项值传递
  • 在函数调用之初需用实际参数对象初始化形式参数对象,需要调用拷贝构造函数
#include<iostream>
using namespace std;
class Myclass
{
private:
	int i;
public:
	Myclass(int n)
	{
		i = n;
	}
	void Set(int n)
	{
		i = n;
	}
	int Get()
	{
		return i;
	}
};
void Sqr(Myclass obj)
{
	obj.Set(obj.Get() * obj.Get());
	cout << "copy of obj has i value of ";
	cout << obj.Get() << endl;
}
int main()
{
	Myclass obj(10);
	Sqr(obj);
	cout << " But,obj.i is unchanged in main: ";
	cout << obj.Get();
	return 0;
}

5.2 使用对象指针作为函数参数

  • 使用对象指针作为函数参数可以实现传址调用,即可在被调用函数中改变调用函数的参数对象的值,实现函数之间的信息传递
  • 同时使用对象指针实参仅将对象的地址值传给形参,而不进行副本的开销 ,可以提高运行效率,减少时空开销

5.3 使用对象引用作为函数参数

  • 用对象引用作形式参数,在调用函数时使得引用参数成为实参对象的别名,不产生新对象,无需另外分配内存空间,也不会调用拷贝构造函数
  • 将对象引用做返回值,可以使用函数的调用作为左值使用
  • 在修改对应实参对象方面,引用具有与指针类似的效果,但是其语法比指针简洁许多,类似于值形式参数的表现形式

6. 静态成员

  • 为了实现一个类的不同对象之间的数据和函数共享,C++提出了静态成员的概念.
  • 静态成员包括:
    • 静态数据成员
    • 静态函数成员
  • 在类的定义中,可以用关键字static声明成员为静态成员,这些静态成员可以在同一个类的不同对象之间提供数据共享
  • 不管这个类创建量多少个对象,但静态成员只有一份拷贝,为所有属于这个类的对象所共享
6.1 静态数据成员
  • 公有的静态数据成员可以在对象定义之前被访问,形式为: 类名::公有静态成员变量名;
  • 在对象定义后还可以通过对象进行访问,形式为: 对象名.公有静态成员变量名;
  • 私有的静态数据成员不能被类的外部函数访问,也不能用对象进行访问
  • 静态数据成员的初始化必须在类外进行,默认值为0,他是在编译时创建并初始化的,所以它在该类的任何对象被创建前就存在

代码理解静态数据成员1

#include<iostream>
using namespace std;
class Ctype
{
private:
	int a;
	static int s; //定义私有静态数据成员s
public:
	void print();
	Ctype(int x = 0);
};
void Ctype::print()
{
	cout << "a = " << ++a << endl;//输出普通数据成员
	cout << "s = " << ++s << endl;//输出静态数据成员,两者进行比较
}
Ctype::Ctype(int x)
{
	a = x;
}
int Ctype::s = 0;//静态数据成员的初始化在类体外进行.前面不能再加static
int main()
{
	Ctype c1, c2, c3;//定义3个对象,都使用默认的参数值
	c1.print();
	c2.print();
	c3.print();
	return 0;
}

运行结果1

a = 1
s = 1
a = 1
s = 2
a = 1
s = 3

代码理解2

#include<iostream>
using namespace std;
class Student
{
private:
	static int count;//声明静态数据成员count,统计学生的总数
	int StudentNo;//普通数据成员,表示学生的学号
public:
	Student()//构造函数
	{
		++count;
		StudentNo = count;
	}
	void print()//成员函数,显示学生的学号和当前学生数目
	{
		cout << "Student" << StudentNo << " ";
		cout << "count=" << count << endl;
	}
};
int Student::count = 0;//在类体外给静态数据成员赋初始值0
int main()
{
	Student student1;//创建第1个学生对象
	student1.print();
	cout << "----------------" << endl;
	Student student2;//创建第2个学生对象
	student1.print();
	student2.print();
	cout << "----------------" << endl;
	Student student3;//创建第3个学生对象
	student1.print();
	student2.print();
	student3.print();
	cout << "----------------" << endl;
	Student student4;//创建第4个学生对象
	student1.print();
	student2.print();
	student3.print();
	student4.print();
	return 0;
}

运行结果2

Student1 count=1
----------------
Student1 count=2
Student2 count=2
----------------
Student1 count=3
Student2 count=3
Student3 count=3
----------------
Student1 count=4
Student2 count=4
Student3 count=4
Student4 count=4
6.2 静态成员函数
  • 在类的定义中,声明为static的成员函数能在类的范围内共享,这样的成员函数称为静态成员函数
  • 静态成员函数属于整个类,是该类对象所共享的成员函数,而不属于类型中的某个对象
  • 静态成员函数只能访问静态数据成员,不能类中其他类型的数据成员或者成员函数进行访问,不可以通过对象或者类名进行调用
  • 定义格式: static 返回类型 静态成员函数名(参数表);
  • 和静态数据成员类似,调用公有静态成员函数的形式为
    • 类名::静态成员函数名(实参表)
    • 对象.静态成员函数名(实参表)
      代码理解
#include<iostream>
using namespace std;
class Ctype
{
private:
	int a;
	static int s;//定义私有的静态数据成员;
public:
	static void Print();//声明静态成员函数
	Ctype();//构造函数的声明
};
void Ctype::Print()
{
	//cout << "a=" << ++a << endl;   错误,静态成员函数不能直接访问非静态数据成员
	cout << "s=" << s << endl;
}
Ctype::Ctype()
{
	a = 0;
	s++;
	cout << "a=" << ++a << endl;
}
int Ctype::s = 0;
int main()
{
	Ctype::Print();//未定义对象时可以直接使用类调用静态成员函数
	Ctype c1, c2;
	c1.Print();//定义对象后可以使用对象调用静态成员函数
	c2.Print();
	Ctype c3;
	c3.Print();
	return 0;
}

运行结果

s=0//类调用静态成员函数输出的结果
a=1//创建对象c1,输出a,这一步s=1;
a=1//创建对象c2,输出a,这一步s=2;
s=2//打印输出s=2;
s=2//同理
a=1//创建对象c3,输出a,这一步s=3;
s=3//打印输出s=3;
  • 注意点:
    • 静态成员函数可以定义成内嵌的,也可以在类外定义,在类外定义时,不用static前缀
    • 私有静态成员函数不能被类外部函数和对象访问
    • 使用静态成员函数的一个原因是,可以用它建立任何对象之前处理静态数据成员,这是不能实现的功能
    • 编译系统将静态成员函数限定为内部连接,与现行文件相连接的其他文件中的同名函数不会和该函数发生冲突,维护了该函数使用的安全性,这是使用静态成员函数的另外一个原因
    • 在一般的成员函数中都隐含了一个this指针,用来指向对象自身,但是在静态成员函数中是没有this指针的
    • 一般而言,静态成员函数不需要访问类中的非静态成员,若确实需要,静态成员函数只能通过对象名访问该对象的非静态成员

7. 友元

  • C++中通过友元实现在类外访问类的私有成员,实现即不放弃私有成员的安全性的情况下,使得一个普通函数或者类的成员函数可以访问到封装于某一类中的信息(私有,保护成员)
  • 友元包括友元函数和友元类
7.1 友元函数
  1. 一个不属于任何类的普通函数声明为当前类的友元,称为当前类的友元函数
  • 友元函数是在类中由关键字friend修饰的非成员函数
  • 友元函数可以访问当前类的所有对象的成员
  • 在类声明友元函数时,可以在类中,也可以在公有,私有或者保护部分(因为它不是类的成员函数嘛,所以任何限制对它都没有用哒)
  • 友元函数可以定义在类内部,也可以定义在类外部
7.2 友元成员
  1. 一个其他类的成员函数声明为当前类的友元函数,称为当前类的友元成员
  • 这种成员函数不仅可以访问自己类对象中的所有成员,还可以访问friend声明语句所在类对象中的所有成员
    代码理解
#include<iostream>
using namespace std;
class N;//在M类前说明,因为在N的定义在M之后
class M
{
	int a, b;
public:
	M(int x, int y)
	{
		a = x;
		b = y;
	}
	void Print()
	{
		cout << "a= " << a << "\tb= " << b << endl;
	}
	void setab(N&);//成员函数
};
class N
{
	int c, d;
public:
	N(int a, int b)
	{
		c = a;
		d = b;
	}
	void Print()
	{
		cout << "c= " << c << "\td= " << d << endl;
	}
	friend void M::setab(N&);//将类的成员函数声明为本类的友元函数
};
void M::setab(N& obj)
{
	a = obj.c;//访问N中的私有数据成员
	b = obj.d;
}

int main()
{
	M m(25, 40);
	N n(55, 66);
	cout << "m: ";
	m.Print();
	cout << "n: ";
	n.Print();
	m.setab(n);
	cout << "m: ";
	m.Print();
	return 0;
}

运行结果

m: a= 25        b= 40
n: c= 55        d= 66
m: a= 55        b= 66

7.3 友元类

  • 当一个类作为另一个类的友元时,则该类的成员函数都是另一个类的友元成员,都可以访问另一个类的所有成员
  • 声明格式: friend 类名;
  • 此语句的位置随意放置
    代码理解
#include<iostream>
using namespace std;
class B;//向前说明,因为在后面定义的A中使用到B
class A
{
	int x;
public:
	A(int a)
	{
		x = a;
	}
	friend class B;//声明友元类
};
class B
{
public:
	void Show(A a)
	{
		cout << "x= " << a.x << endl;
	}
};
int main()
{
	A a(10);
	B b;
	b.Show(a);
	return 0;
}

运行结果

x= 10

注意

  • 友元关系是单向的,只有双方都声明为自己的友元时才可以实现互访
  • 友元关系不具备传递性,A是B的友元,B是C的友元,但是A不一定是C的友元

8. 对象成员

  • 如果一个类的对象是另一个类的数据成员,则称为数据成员的对象成员
class A
{
//...
};
class B
{
A a;//类A的对象a为类B的对象成员
public:
//...
};
  • 含有对象成员的类的构造函数
类名::类名(形参表): 对象成员1(参数表),对象成员2(参数表),...,对象成员n(参数表n)
{
//构造函数体
}

-调用构造函数类名::类名()时:
+ 首先按各对象成员在类声明中的顺序依次调用它们的构造函数,对这些对象初始化,而不是按照初始表的顺序进行初始化
+ 最后再执行x::x()的函数体
+ 析构函数的调用顺序与此相反

#include<iostream>
using namespace std;
class Date  //定义Date类
{
private:
	int year;
	int month;
	int day;
public:
	Date(int y, int m, int d)
	{
		cout << "构造 Date" << endl;
		year = y;
		month = m;
		day = d;
	}
	void show()
	{
		cout << year << "." << month << "." << day << endl;
	}
};

class Time    //定义Time类
{
private:
	int hour;
	int minute;
	int second;
public:
	Time(int h, int m, int s)
	{
		cout << "构造 Time" << endl;
		hour = h;
		minute = m;
		second = s;
	}
	void show()
	{
		cout << hour << ":" << minute << ":" << second << endl;
	}
};

class Shedule  //定义Schedule类
{
private:
	int number;
	Date date;
	Time time;
public:
	//含有对象成员的类的构造函数声明及定义
	Shedule(int num, int a, int b, int c, int d, int e, int f) :date(a, b, c), time(d, e, f)
	{
		cout << "构造 Schedule" << endl;
		number = num;
	}
	void show()
	{
		cout << "number" << number << ":";
		date.show();
		date.show();
	}
};

int main()
{
	Shedule obj1(1, 2003, 7, 8, 12, 10, 0);
	obj1.show();
}

运行结果

构造 Date
构造 Time
构造 Schedule
number1:2003.7.8
2003.7.8
8.1 对象成员使用的注意点
  1. 对象成员的构造函数先于本类构造函数被执行,也就是先构造对象成员,再构造本类对象
  2. 如果对象成员所属类的构造函数不带参数,则在本类的构造函数后面不需要初始化列表来初始化对象成员,但是对象成员所属类的构造函数一定时调用的,与是否初始化列表中出现无关
  3. 如果要调用对象成员所属类的带参构造函数,则在本类构造函数后面必须提供初始化列表,以成员对象名(实在参数表)的形式出现在初始化列表中
  4. 构造对象成员的实在参数通常来源于本类构造函数的形式参数列表中
  5. 构造函数的调用顺序为:对象成员所属类的构造函数、本类构造函数,如果对象成员不只一个,则按照在新类定义时对象成员出现的先后次序依次调用各类的构造函数,而与在本类构造函数初始化表中各对象成员出现的顺序无关。
  6. 析构函数的调用顺序始终与构造函数的调用顺序正好相反,即先调用本类的析构函数,再调用对象成员所在类的析构函数。
  7. 对象成员的数据成员和成员函数应由该对象成员来访问。

9. 常对象

  • 既需要共享,又需要防止改变的数据,应该声明为常量进行保护,因为常量在程序运行期间是不可以改变的
  • 这些常量需要用关键字const进行定义
  • const不仅修饰类的对象,还可以修饰类的数据成员和成员函数,分别称为:
    • 常数据成员
    • 常成员函数
    • 常对象
9.1 常数据成员
  • 类的数据成员由const说明
  • 如果类中说明了常数据成员,则构造函数只能通过初始化列表对该数据成员进行初始化
  • 其他任何函数都不能对常数据成员进行修改,只能进行访问

代码理解

#include<iostream>
using namespace std;
class InitiData
{
	int x;
	int& rx;//定义一个引用成员
	const double pi;//定义常数据成员
public:
	InitiData(int x1);
		void Display();
};
InitiData::InitiData(int x1) :rx(x1), pi(3.14)  //对常数据成员,引用成员
{                                               //只能通过初始化列表进行初始化
	x = x1;
}                     
void InitiData::Display()
{
	cout << "x=" << x << "\trx=" << rx << "\tpi=" << pi << endl;
}
int main()
{
	InitiData id(100);
	id.Display();
	return 0;
}

运行结果

x=100   rx=100  pi=3.14

9.2 常成员函数
  • 声明格式: 类型 函数名(参数表) const;
  • 注意点:
    • const是函数类型的一个组成部分,因此在常成员函数的原型声明及函数定义的首部都要使用关键字const。
    • 常成员函数不能修改本类的数据成员,也不能调用该类中没有由关键字const修饰的成员函数,从而保证了在常成员函数中不会修改数据成员的值。
    • 关键字const可以作为与其他成员函数重载的标志
    • 访问属性为public的常成员函数可以通过该类的任何对象调用。
    • 常成员函数在原型声明及函数定义的首部都不能缺少const关键字,此时关键字const参与区分函数重载
9.3 常对象
  • 如果在说明对象时用const修饰,则被说明的对象为常对象。
  • 常对象的数据成员值在对象的整个生存期内不能被改变。常对象的定义格式为:const 类名 对象名;或 类名 const 对象名;
  • 特点:
    • 常对象在定义时必须进行初始化,而且其数据成员的值在对象的整个生存期间内不能被改变。也就是说,常对象必须进行初始化,而且不能被更新。
    • 由于常对象的值(包括所有的数据成员的值)不能被改变,因此,通过常对象只能调用类中那些不改变数据成员值的成员函数(即常成员函数),而不能调用类中的其他普通成员函数。

代码理解

#include<iostream>
using namespace std;
class Person
{
private:
	int age;
	char* name;
public:
	Person(int, char*);
	~Person();
	void Print();   //重载函数,用于输出的普通成员函数
	void Print() const;  //重载函数,用于输出的常成员函数,const参与重载
	void ModifyAge();
};
Person::Person(int n, char* na)   //构造函数的定义
{
	age = n;
	name = new char[strlen(na) + 1];
	strcpy(name, na);
}
Person::~Person()   //析构函数的定于
{
	delete[]name;
}
void Person::Print()   //普通成员函数print()的定义
{
	cout <<" age: " << age << " name: " << name << endl;
	cout <<" This is general Print(). " << endl;
}
void Person::Print() const   //常成员函数Print()的定义,const不可省略
{
	cout << " age: " << age << " name: " << name << endl;
	cout << " This is const print()." << endl;
}
void Person::ModifyAge()   // 用于修改年龄的普通成员函数
{
	age++;
}
int main()
{
	const Person p1(17,"wu");   //定义常对象必须初始化
	cout <<" output const object p1" << endl;
	p1.Print();               //常对象调用常成员函数
	Person p2(18," zhang");    //定义普通的对象
	cout <<" output general object p2 " << endl;
	p2.ModifyAge();        //可以修改数据成员
	p2.Print();           //普通对象调用普通的成员函数
	return 0;
}


运行结果

output const object p1
age: 17  name: wu
  This is const Print()   //此时调用的是带const的常成员函数
  output general object p2
  age: 19  name: zhang
  This is general Print()  //此时调用的是普通的成员函数

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值