C++语法补习课——对象特性

来源:黑马程序员;在正式开启Qt学习之前最后过一遍C++语法,具体内容参考目录。

预计从第104讲学到第112讲。

第104讲 设计案例1 立方体类

课堂要求

设计立方体类(Cube)要求:

①求出立方体的面积和体积;

②分别用全局函数和成员函数判断两个立方体是否相等。

对象作为形参

在C++中,将实体化的类对象作为函数的参数,通常有两种方式:值传递(Pass by Value)和引用传递(包括指针传递和引用传递)。这两种方式各有优缺点,适用于不同的场景。

 值传递

当你通过值传递的方式将一个类对象作为参数传递给函数时,实际上是将对象的副本传递给函数。这意味着在函数内部对对象的任何修改都不会影响到原始对象。

#include <iostream>  
using namespace std;  
  
class MyClass {  
public:  
    int value;  
    MyClass(int val) : value(val) {}  
};  
  
void func(MyClass obj) {  
    obj.value = 100; // 修改的是副本的value  
    cout << "Inside func: " << obj.value << endl;  
}  
  
int main() {  
    MyClass myObj(50);  
    func(myObj); // 传递myObj的副本  
    cout << "Outside func: " << myObj.value << endl; // 输出仍然是50  
    return 0;  
}

 引用传递

引用传递包括两种形式:通过指针和通过引用。

通过指针传递:在函数参数中使用指针类型,这样你就可以在函数内部通过指针来访问原始对象。

void func(MyClass* objPtr) {  
    objPtr->value = 100; // 修改的是原始对象的value  
}  
  
// 在main函数中调用  
MyClass myObj(50);  
func(&myObj); // 传递myObj的地址

通过引用传递:C++提供了引用(&)这一特殊类型,它允许函数以引用的方式接收参数,这样函数内部对对象的修改就会反映到原始对象上。

void func(MyClass& obj) {  
    obj.value = 100; // 修改的是原始对象的value  
}  
  
// 在main函数中调用  
MyClass myObj(50);  
func(myObj); // 直接传递myObj,无需使用&

代码示例

#include <iostream>
#include <string>
using namespace std;

class Cube {
 public:
	 void setCubeInfo(Cube &obj,int length,int breadth,int height);  //设置正方体信息
	 void printCubeInfo(Cube &obj);                                  //通过引用的方式传递实体化对象
	 int  getCubeL(Cube &obj);
	 int  getCubeB(Cube &obj);
	 int  getCubeH(Cube &obj);
	 int  getCubeArea(void);
	 int  getCubeSpace(void);
	 bool beSameCube(Cube& obj);

 private:
	int l = 0;  //底面长度
	int b = 0;  //底面宽度
	int h = 0;  //立方体高度
};

void Cube::setCubeInfo(Cube& obj, int length, int breadth, int height)  
{
   obj.l = length;
   obj.b = breadth;
   obj.h = height;
}

void Cube::printCubeInfo(Cube& obj)
{
	string message = "立方体信息:长度为" + std::to_string(obj.l) + 
		",宽度为"+ std::to_string(obj.b) + 
		",高度为"+std::to_string(obj.h)+"\n";
	printf("%s",(message).c_str());

}

int Cube::getCubeL(Cube& obj)
{
	return obj.l;
}

int Cube::getCubeB(Cube& obj)
{
	return obj.b;
}

int Cube::getCubeH(Cube& obj)
{
	return obj.h;
}


int Cube::getCubeArea(void)
{
	int area = 0;
	area = 2 * b * l + 2 * l * h + 2 * b * h;
	printf("立方体的表面积:%d\n", area);
	return area;
}

int Cube::getCubeSpace(void)
{
	int space = 0;
	space = l * b * h;
	printf("立方体的体积:%d\n", space);
	return space;
}

bool Cube::beSameCube(Cube& obj)
{
	if ((b==obj.getCubeB(obj))&&(h== obj.getCubeH(obj))&&(l== obj.getCubeL(obj)))
	{
		return 1;
	}
	else {
		return 0;
	}
}

bool isSame(Cube &obj1, Cube &obj2)
{
	if ((obj1.getCubeB(obj1) == obj2.getCubeB(obj2))
		&& (obj1.getCubeH(obj1) == obj2.getCubeH(obj2))
		&& (obj1.getCubeL(obj1) == obj2.getCubeL(obj2)))
	{
		return 1;
	}
	else {
		return 0;
	}
}


int main()
{
	Cube A;
	A.setCubeInfo(A,6,8,10);
	A.printCubeInfo(A);
	A.getCubeArea();
	A.getCubeSpace();

	Cube B;
	B.setCubeInfo(B, 6, 8, 11);
	if (isSame(A, B) != 0) {
		printf("相同\n");
	}
	else {
		printf("不同\n");
	}

	bool ret = A.beSameCube(B);
	if (true==ret) {
		printf("相同\n");
	}
	else {
		printf("不同\n");
	}

	system("pause");
	return 0;
}

第105讲 判断点与圆的关系

课堂需求

设计一个圆形类和一个点类,计算点和圆的关系

代码示例

#include <iostream>
#include <cmath>
using namespace std;

//点和圆的关系有点在圆内、圆外、园上
class Circle {
  private:
	  int centerX = 0;
	  int centerY = 0;
	  int radius = 0;
  public:
	  void setCircleInfo(int x, int y, int r);
	  int  getCenterX();
	  int  getCenterY();
	  int  getCircleR();
};

int Circle::getCenterX()
{
	return centerX;
}

int Circle::getCenterY()
{
	return centerY;
}

int Circle::getCircleR()
{
	return radius;
}

void Circle::setCircleInfo(int x, int y, int r)
{
	centerX = x;
	centerY = y;
	radius = r;
}

class Point {
   private:
	   int pointX;
	   int pointY;
   public:
	   void setPointInfo(int x, int y);
	   int  getPointX(void);
	   int  getPointY(void);

};

void Point::setPointInfo(int x, int y)
{
	pointX = x;
	pointY = y;
}

int Point::getPointX()
{
	return pointX;
}

int Point::getPointY()
{
	return pointY;
}

int locationJude(Circle &obj1,Point &obj2)
{
	float distance = 0;
	int disxSquare = 0;
	int disySquare = 0;
	disxSquare = (obj2.getPointX() - obj1.getCenterX()) * (obj2.getPointX() - obj1.getCenterX());
	disySquare = (obj2.getPointY() - obj1.getCenterY()) * (obj2.getPointY() - obj1.getCenterY());

	distance = sqrt(disxSquare+disySquare);  //cmath库内的开根函数

	if (distance == obj1.getCircleR()) {
        //圆上
		return 1;
	}
	else if (distance > obj1.getCircleR()) {
		//圆外
		return 0;
	}
	else {
		//圆内
		return -1;
	}
}

int main()
{
	Circle c1;
	Point  p1;

	c1.setCircleInfo(0,0,10);
	p1.setPointInfo(5,5);

	int ret = 0;
	ret = locationJude(c1, p1);

	switch(ret){
	  case -1:
		  cout << "点在圆内\n";
		  break;

	  case  0:
		  cout << "点在圆外\n";
		  break;

	  case  1:
		  cout << "点在圆上\n";
		  break;

	  default:
		  break;

	}

	system("pause");
	return 0;
}

第106讲 构造函数与析构函数

基本概念

对象的初始化和清理是两个非常重要的安全问题,一个对象或者变量如果没有初始状态,对其使用后果是未知的。同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题。          c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此即使我们不提供构造和析构,编译器也会提供自带的构造函数和析构函数(空实现)。

注意,构造函数与析构函数的定义都需要要在类的public作用域下实现!

构造函数

主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

 析构函数

主要作用在于对象销毁前系统自动调用,执行一些清理工作。

语法

构造函数语法

类名 (){...}

1.构造函数,没有返回值也不写void

2.函数名称与类名相同

3.构造函数可以有参数,因此可以发生重载

4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法

~ 类名 () {...}

1.析构函数,没有返回值也不写void

2. 函数名称与类名相同,在名称前加上符号~
3.析构函数不可以有参数,因此不可以发生重载

4.程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

代码示例

#include <iostream>
using namespace std;

class Person {
	private:
    

	public:
	Person() {
		cout << "构建函数已调用" << endl;
	}

	~Person() {
		cout << "析构函数已调用\n";
	}
};

void test01(void)
{
	Person p1;
}


int main()
{
	test01();



	system("pause");
	return 0;
}

输出结果

p1这个实例化的对象是一个在函数中定义的局部变量,它会被编译器分配到栈区,在函数执行结束之后会被销毁。因此在整个进程的结束,析构函数伴随着对象被销毁而调用。

第107讲 构造函数的分类以及调用

构造函数分类有两种:

按参数分为有参构造无参构造(默认构造)

//有参构造
Person(int a)
{
  cout << "Person的有参构造函数调用" << endl;
}

//默认构造
Person()
{
  cout << "Person的无参构造函数调用" << endl;
}

按类型分为普通构造拷贝构造,拷贝构造通过引用传参拷贝一份已经实例化的对象。

//拷贝构造
Person(const Person& p)
{

}

构造函数调用方式有三种:

括号法

	//括号法
	Person p1;      //无参构造函数的调用
	Person p2(10);  //有参构造函数的调用
	Person p3(p1);  //拷贝构造函数的调用

输出结果:

注意,调用默认构造函数时不要加( ),防止编译器认为语句在声明函数,而不是认为在创建对象。

显示法

	//显示法
	Person p1;
	Person p2 = Person(100);
	Person p3 = Person(p2);

输出结果:

注意1赋值符号右侧的部分,Person(100)的写法属于创建匿名对象,当前执行结束后会被系统立刻回收。 

	Person(18);  //匿名对象
	cout << "Hello World!" << endl;

输出结果:

注意2不要用拷贝构造函数初始化匿名对象

写法是Person(p3);编译器会判定p3重定义:Person(p3)=Person p3;

隐式转换法

	//隐式转换法
	Person p4 = 10; // 等价于Person p4=Person(10);
	Person p5 = p4; //调用拷贝构造

输出结果:

第108讲 拷贝构造函数调用时机

C++中拷贝构造函数调用时机通常有三种情况:

①使用一个已经创建完毕的对象来初始化一个新对象

代码示例:

#include <iostream>
using namespace std;

class Person {
      private:
		  int myAge = 0;
      public:
		  void printAge(void);

		  Person()
		  {
			  printf("Person的默认构造函数调用\n");
		  }

		  ~Person()
		  {
			  printf("Person的析构函数调用\n");
		  }

		  Person(int age)
		  {
			  printf("Person的有参构造函数调用\n");
			  myAge = age;
		  }

		  Person(const Person &p)
		  {
			  printf("Person的拷贝构造函数调用\n");
			  myAge = p.myAge;
		  }
};

void Person::printAge(void)
{
	cout << "Person's age is " <<myAge << endl;
}


void test01(void)
{
	Person p1(20);
	p1.printAge();
	Person p2(p1);
	p2.printAge();

}

int main()
{
	test01();



	system("pause");
	return 0;
}

输出结果:

②值传递的方式给函数参数传值

代码示例:

void doWork(Person p)
{

}

void test02(void)
{
	Person p;
	doWork(p);
}

输出结果:

③以值方式返回局部对象

Person makeWork(void)
{
	Person p1;
	cout << (int*)&p1 << endl;
	return p1;
}

void test03(void)
{
	Person p = makeWork();
	cout << (int*)&p<< endl;
}

输出结果:

第109讲 构造函数的调用规则

默认情况下,c++编译器至少给一个类添加3个函数

1.默认构造函数(无参,函数体为空)

2. 默认析构函数(无参,函数体为空)

3. 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

1.如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造

2. 如果用户定义拷贝构造函数,c++不会再提供其他构造函数

代码示例:

#include <iostream>
using namespace std;

class Person {
private:
	int pAge;

public:
	Person()
	{
		printf("Person的默认构造函数调用\n");
	}

	Person(int age)
	{
		printf("Person的有参构造函数调用\n");
		pAge = age;
	}

	Person(const Person &p)
	{
		printf("Person的拷贝构造函数调用\n");
		pAge = p.pAge;
	}

	~Person()
	{
		printf("Person类的某个对象已被销毁\n");
	}

	void setpAge(int newAge);
	void printAge(void);
};

void Person::setpAge(int newAge)
{
	pAge = newAge;
}

void Person::printAge(void)
{
	printf("age = %d;\n",pAge);
}

void test01(void)
{
	Person p1;
	p1.setpAge(18);

	Person p2(p1);
	p2.printAge();
}

int main()
{
	test01();


	system("pause");
	return 0;
}

输出结果:

如果我们把自己定义的拷贝构造函数注释掉,再来试试运行,可以看到p2的age依旧是18,由于 pAge 是一个基本类型的变量,并且类中没有其他资源需要管理,所以编译器生成的默认拷贝构造函数已经足够。

第110讲 深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作;

深拷贝:在堆区重新申请空间,进行拷贝操作。

示例:

#include <iostream>
using namespace std;

class Person {
public:
	int m_age = 0;

	Person()
	{
		printf("Person的默认构造函数调用\n");
	}

	Person(int age)
	{
		m_age = age;
		printf("Person的有参构造函数调用\n");
	}

	~Person()
	{
		cout<<"Person的析构函数调用" << endl;
	}

};

void test01(void)
{
	Person p1(18);
	cout << "p1的年龄是:" << p1.m_age << endl;

	Person p2(p1);
	cout << "p2的年龄是:" << p2.m_age << endl;

}



int main()
{
	test01();


	system("pause");
	return 0;
}

编译器自动创建的拷贝构造函数属于浅拷贝,p1和p2之间进行了简单地参数传递。由于目前只有m_age这个存储在栈区的成员变量,浅拷贝足以应付,但涉及到堆区内存时,可能就会出现问题。

错误示例:

#include <iostream>
using namespace std;

class Person {
public:
	int m_age = 0;
	int* m_height;   //通过指针把变量开辟到堆区

	Person()
	{
		printf("Person的默认构造函数调用\n");
	}

	Person(int age,int height)
	{
		m_age = age;
		m_height=new int(height);   //利用new把变量创建在堆区
		printf("Person的有参构造函数调用\n");
	}

	~Person()
	{
		if (m_height != NULL) {
			delete m_height; //手动释放堆区空间
			m_height = NULL; //防止野指针
		}

		cout<<"Person的析构函数调用" << endl;
	}

};

void test01(void)
{
	Person p1(18,179);
	cout << "p1的年龄是:" << p1.m_age <<",p1的身高是:"<<*p1.m_height<< endl;

	Person p2(p1);
	cout << "p2的年龄是:" << p2.m_age << ",p2的身高是:"<<*p2.m_height << endl;

}



int main()
{
	test01();


	system("pause");
	return 0;
}

在这个示例这种,对象p1、p2的析构函数重复地对堆区空间进行了释放,引入了致命失误。这时候我们就应该引入深拷贝了。

	//引入深拷贝
	Person(const Person& p)
	{
		cout << "Person拷贝构造函数地调用" <<endl;
		m_age = p.m_age;
		//深拷贝操作
		m_height = new int(*(p.m_height));
	}

  介绍一个Visual Studio的使用技巧,先生成(编译)修改语法错误后运行。

可以看到编译器使用我们提供的拷贝构造函数正常地实现了用p2拷贝p1。

注意:如果有在堆区开辟的对象属性,一定要自己编写拷贝构造函数

第111讲 初始化列表

C++的初始化列表机制可以在构造函数里面初始化对象属性。

语法:构造函数 ( ):属性1(值1),属性2(值2)...{  } 

对比传统的对象成员属性初始化操作:

#include <iostream>
using namespace std;

class Person
{
  public:
	int    age;
	float  grade;
	string name;
	
	//传统的对象初始化操作
   #if 0
	Person(int a, float g, string n)
	{
		age = a;
		grade = g;
		name = n;
	}
   #endif 

	//初始化列表
	Person() :age(20), grade(100), name("Zeus")
	{
	
	}
};

void test01()
{
#if 0
	Person p0(18,95,"Keria");
	cout << "p0的年龄为:" << p0.age   <<endl;
	cout << "p0的成绩为:" << p0.grade <<endl;
	cout << "p0的名字为:" << p0.name  <<endl;
#endif

	Person p1;
	cout << "p1的年龄为:" << p1.age << endl;
	cout << "p1的成绩为:" << p1.grade << endl;
	cout << "p1的名字为:" << p1.name << endl;
}

int main()
{
	test01();





	system("pause");
	return 0;
}

虽然现在可以用默认构造函数写法,但这个写法把对象的初始化实例写死了非常不方便,可以尝试下面的写法:

	//初始化列表
	Person(int a, float g, string n) :age(a), grade(g), name(n)
	{
	
	}

第112讲 类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员。这里我自己写了一个案例,有一个点对象,成员属性为横坐标x与纵坐标y;还有一个圆对象,成员属性为圆心(点对象)与半径。代码示例如下:

#include <iostream>
#include <cmath>
using namespace std;

class Point {
public:
	int x;
	int y;

	Point(void)
	{
		cout << "Point的无参构造函数调用" << endl;
	}
};

class Circle {
public:
	Point center;
	int radius;

	Circle(Point c,int r)
	{
		center = c;
		radius = r;
		cout << "Circle的有参构造函数调用" << endl;
	}
};

//判断圆是否包含原点
bool originJudge(const Circle &c)
{
	float distance = 0;
	int disxSquare = 0;
	int disySquare = 0;

	disxSquare = c.center.x * c.center.x;
	disySquare = c.center.y * c.center.y;

	distance = sqrt(disxSquare + disySquare);  //cmath库内的开根函数

	if (distance < c.radius) {
		cout << "此圆包含原点"<<endl;
	    return true;
	}
	else {
		cout << "此圆不包含原点" << endl;
		return false;
	}

}


int main()
{
	Point center0;
	center0.x = -6;
	center0.y = 6;

	Circle circle1(center0,3);
	originJudge(circle1);



	system("pause");
	return 0;
}

输出结果:

可以看到在这种Circle包含Point的情况下,程序还是优先调用了Point的构造函数给Circle构造了圆心对象。得出结论:当其他类对象作为本类成员时,构造时先构造其他类对象,再构造自身。

我们可以再添加一些代码捕捉析构函数的执行顺序:

#include <iostream>
#include <cmath>
using namespace std;

class Point {
public:
	int x;
	int y;

	Point(void)
	{
		cout << "Point的无参构造函数调用" << endl;
	}

	~Point(void)
	{
		cout << "Point的析构函数调用" << endl;
	}
};

class Circle {
public:
	Point center;
	int radius;

	Circle(Point c,int r)
	{
		center = c;
		radius = r;
		cout << "Circle的有参构造函数调用" << endl;
	}

	~Circle(void)
	{
		cout << "Circle的析构函数调用" << endl;
	}
};

//判断圆是否包含原点
bool originJudge(const Circle &c)
{
	float distance = 0;
	int disxSquare = 0;
	int disySquare = 0;

	disxSquare = c.center.x * c.center.x;
	disySquare = c.center.y * c.center.y;

	distance = sqrt(disxSquare + disySquare);  //cmath库内的开根函数

	if (distance < c.radius) {
		cout << "此圆包含原点"<<endl;
	    return true;
	}
	else {
		cout << "此圆不包含原点" << endl;
		return false;
	}

}

void test01(void)
{
	Point center0;
	center0.x = -6;
	center0.y = 6;

	Circle circle1(center0, 3);
	originJudge(circle1);
}

int main()
{
	test01();



	system("pause");
	return 0;
}

注意测试案例需要封装为函数,这样才会被编译器规划到栈区之中。

输出结果:

可见,析构函数的执行顺序与构造函数恰好相反,系统会先用Circle的析构函数销毁圆对象,再用Point的析构函数销毁圆心对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值