C++学习记录2——类和对象

5 篇文章 0 订阅

一、类和对象

1.类和对象概念

万事万物皆为对象,对象上有属性和方法。
具有相同的属性和方法(相同性质)的对象可被抽象称为类。

类中的属性和对象统称为成员
属性也称成员属性,成员变量,也称字段(field);方法也称成员方法,成员函数。

2.类和对象的定义

在这里插入图片描述

const double PI = 3.14;
class Circle
{
   public:  //公共权限(若不写,默认为私有)
     int r;  //属性:圆的半径(变量)
     double calculateS()  //方法:求圆的周长(函数)
     {
     	return 2*PI*r;
     }
};

int main()
{
	//通过圆类,创建具体的圆对象 (实例化)
	Circle c1;
	//给圆对象的属性进行赋值
	c1.r = 10;
	cout<<"圆的周长为:"<<c1.calculateS()<<endl;

同一个类中的方法可以直接调用,不同类要用类名.方法

3.struct 和 class 的区别

唯一区别在于默认访问权限不同

  • struct默认权限为公有
  • class 默认权限为私有

二、封装

1.封装的意义

  • 将属性和方法作为一个整体表现,隐藏实现细节,直接调用
class Circle
{
   public: 
     int r;  //属性:圆的半径(变量)
     double calculateS()  //方法:求圆的周长(函数)
     {
     	return 2*PI*r;
     }
     void getR(int R)  //可通过方法为属性赋值
     {
     	r = R;
     }
};
  • 将属性和行为加以权限控制,保证安全

访问权限:

访问权限有3种:
1.public——公共权限(成员类内类外都可访问)
2.protected——保护权限(成员类内可访问,类外不可访问,子类可访问)
3.private——私有权限(成员类内可访问,类外不可访问,子类不可访问)

class Person
{
   public: 
     string name; 
   protected:
     int age;
   private:
     int password;
   private:
     void put()  //类内都可访问
     {
     	name = "张三";
     	age = 20;
     	password = 123456;
     }
};

int main()
{
	Person P1;
	//P1.age = 10;  //类外不可访问
	//P1.put(); 

成员属性的私有化:

优点:

  1. 将所有成员属性设为私有,可自己控制读写权限
  2. 对于写权限,可检测数据的有效性
class Person
{
  private:
     string m_name;  //只读
     int m_age;  //可读可写
     int m_password;
  public:  //通过公共接口操作
     string getName()  //通过公共函数获取私有属性
     {
     	return m_name;
     }
     void setAge(int age)  //通过公共函数修改私有属性
     {
     	if (age<0)  //判断检测数据的有效性
     	{
     		cout<<"你个碎皮"<<endl;
     		return;
     	}
     	if (age>150)
     	{
     		cout<<"你个老妖精"<<endl;
     		return;
     	}
     	else 
     	{
     		m_age = age;
     	}
     }
     int getAge()
     {
     	return m_age;
     }
};

int main()
{
	Person P1;
	setAge(200);
	getAge();
}	

作用域运算符:

两个冒号::
将类 Point 创建在头文件里,在源文件中包含,调用Point类中的函数需要用作用域运算符表明作用域。

#include "point.h"

int Point::getX()  //表明是Poin作用域下的getX()函数
{
	return X;
}

三、对象的初始化和清理

每个对象都有初始设置和对象销毁前的清理数据设置

1.构造函数和析构函数

对象若未初始化,使用后果未知;未及时清理,可能有安全问题
C++中有构造函数和析构函数会被编译器自动调用,完成对象初始化和清理工作。如果我们不提供构造和析构,编译器会提供(强制),但编译器提供的构造和析构是空实现(函数中无代码)。

  • 构造函数(也称构造方法或构造器):主要作用在于创建对象时为对象成员属性赋值,自动调用
  • 析构函数:对象销毁前系统自动调用,执行一些清理工作

构造函数语法: 类名( ) { }

  1. 没返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数有参数,因此可以发生重载
  4. 程序调用对象时会自动调用构造,无需手动调用,且只调用1次

析构函数语法: ~类名( ) { }

  1. 没返回值也不写void
  2. 函数名称与类名相同,名称前加 ~ 符号
  3. 析构函数不可以有参数,不可以发生重载
  4. 程序在对象销毁前会自动调用析构,无需手动调用,且只调用1次
class Person
{
public:  //需要公共才可调用
	Person()
	{
		cout<<"构造函数的调用"<<endl;
	}
	~Person()
	{
		cout<<"析构函数的调用"<<endl;
	}
};

void test1()
{
	Person P1;  //在栈上的数据,test1执行后会释放对象(销毁)
}
int main()
{
	test1();  //创建对象会自动调用Person()
	          //test1执行后自动调用~Person()
	Person P2;  //只调用Person()
				//main()函数执行完后会调用~Person()
}	

2.构造函数的分类及调用

两种分类方式:

  • 按参数分为:有参构造和无参构造(默认)
  • 按类型分为:普通构造和拷贝构造

三种调用方式:

  • 括号法
  • 显示法
  • 隐式转换法

拷贝构造有参,参数为另一个类,将另一个类的全部属性拷贝到此类上
需要加 const 防止改变原类,需要使用引用传递

class Person
{
	int age;
public:  
	Person(const Person01 &p)
	{
		cout<<"拷贝构造函数的调用"<<endl;
		age = p.age;
	}
};
void test1()
{
	//1.括号法
	Person P1;  //调用默认构造函数,不要加(),否则会被认为是函数的声明
	Preson P2(10);  //调用有参构造函数
	Person P3(P2);  //调用拷贝构造函数

	//2.显示法
	Person P1;
	Person P2 = Person(10);
	Person P3 = Person(P2);

	//3.隐式转换法
	Person P2 = 10;  //相当于Person P2 = Person(10);
	Person P3 = P2;
}
int main()
{
	test1();  
}	

PS:

  1. Person(10) 为匿名对象 Person P2 = Person(10);起名为P2
    匿名对象的特点:当前行执行结束后,系统会立即回收匿名对象
  2. 不要用拷贝构造函数初始化匿名对象
    Person(P3)会被编译器认为是Person P3 //(对象声明)

3.拷贝构造函数的调用时机

通常有三种情况:

  1. 使用一个已创建好的对象来初始化一个新对象(最常用)
  2. 值传递的方式给函数参数传值(相当于创建临时拷贝)
class Person
{
	int age;
};
void dowork(Person p)  //值传递
{
}
int mian()
{
	Person P;
	dowork(P);
}
  1. 以值方式返回局部对象
class Person
{
	int age;
};
Person dowork1()  //局部对象
{
	Person P1;  //P1执行完后就被释放
	return P1;  //拷贝一个新对象(P1’)返回给p
}
int mian()
{
	Person p = dowork1();  //p不是P1,是P1’
}

4.构造函数的调用规则

默认情况下,C++编译器至少给一个类添加三个函数:
1.默认构造函数 2.默认析构函数 3.默认拷贝构造函数,对属性进行值拷贝

调用规则:
1.如果用户定义有参构造函数,C++不再提供默认无参构造,提供默认拷贝构造
2.如果用户定义拷贝构造函数,C++不再提供其他构造函数

class Person
{
	int age;
public:  
	Person(int age1)  //已有有参构造函数
	{
		cout<<"有参构造函数的调用"<<endl;
		age = age1;
	}
};
int mian()
{
	//Person P;  //报错,没有默认构造函数可调用
	Person P(10);
}

5.深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作

class Person
{
public:
	Person()
	{
		cout << "默认构造函数" << endl;
	}
	Person(int age,int height)
	{
		m_age = age;
		m_height = new int(height);
		cout << "有参构造函数" << endl;
	}
	~Person()
	{
		//析构代码,将堆区开辟数据释放
		if (m_height != NULL)
		{
			delete m_height;
			m_height = NULL;
		}
		cout << "默认析构函数" << endl;
	}
	int m_age;
	int* m_height;
};

void test1()
{
	Person P1(18,180);
	cout << "P1的年龄:" << P1.m_age <<"身高为:"<<P1.m_height<< endl;
	Person P2(P1);
	cout << "P1的年龄:" << P1.m_age << "身高为:" << P1.m_height << endl;
}

int main()
{
	test1();
	return 0;
}

以上代码执行会报错,原因在于析构代码2次执行,重复释放空间
请添加图片描述
解决方法:通过深拷贝,重新开辟空间

Person(const Person &p)
	{
		m_age =p.m_age;
		//m_height = p.m_height;  //编译器默认实现
		//深拷贝
		m_height = new int(*p.m_height);
		cout << "拷贝构造函数" << endl;
	}

6.初始化列表

用于初始化对象的属性构造函数():属性1(值1),……{}

class Person
{
	int age1;
	int age2;
};
Person():age1(10),age2(20)
{

}
int main()
{
	Person p;
}

class Person
{
	int age1;
	int age2;
};
Person(int a,int b):age1(a),age2(b)
{

}
int main()
{
	Person p(20,10);
}

7.类对象作为类成员

类中的成员可以是另一个类的对象,称该成员为对象成员

class A 
{
public:
	setA()
	{
		A a;
	}
};
class B
{
	int n;
	A a;  //对象成员
};

当其他类的对象作为本类成员时,构造时先构造类对象,再构造自身,析构时先析构自身,再析构类对象

8.静态成员

成员变量、成员函数前加static
静态成员变量:
所有对象共享同一份数据,在编译阶段分配内存,类内声明,类外初始化
静态成员函数:
所有对象共享同一个函数,静态成员函数只能访问静态成员变量
静态成员函数也有访问权限

class Person
{
public:
	static void fun()
	{
		a=10;
		//b=10;  //错误
	}
	static int a;
	int b;  //属于特定对象,静态函数调用无法区分是哪个对象的b
};

int a = 0;
int main()
{
	Person p;  //通过对象访问
	p.fun();  //类外初始化

	Person::fun();  //通过类名访问,不创建对象也可访问
}

四、C++对象模型与this指针

1.成员变量和成员函数分开存储

类内成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上

空类空对象占用内存空间为:1字节
编译器会给每个空对象分配1字节空间,为了区分空对象占内存的位置。每一个空对象也有独一无二的内存地址。

class Person
{
public:
	 void fun()  //(静态/非静态)成员函数,不属于类的对象上
	{}
	static int a;  //静态成员变量,不属于类的对象上
	int b;  //只有非静态成员变量,属于类的对象上
};
int Person::b=0;  //类外初始化
int main()
{
	Person p;  
}

上面的Person p占用空间为4字节(只有int b)

2.this指针

每个非静态成员函数只会诞生一份函数实例,多个同类型的对象会公用一块代码。
C++提供特殊的对象指针:this指针,指向被调用的成员函数所属的对象
this指针隐含在每一个非静态成员函数内,不需定义,可直接使用

this指针的本质是指针常量,指向不可修改

用途:

  • 当形参和成员变量同名时,可用this指针区分
class Person
{
public:
	Person(int age)  //这前三个age会被认为是同一个量
	{
		age = age;  //输出乱码
		this->age = age;  //正确,this age 与下面 int age 相同
	}
	int age; 
	int m_age;  //区分变量名,member age
};
int main()
{
	Person p1(18);
	cout << "p1的年龄:" << p1.age << endl;
}
  • 在类的非静态成员函数中返回对象本身,可使用return * this
class Person
{
public:
	Person(int age)
	{
		this->age = age;
	}

	Person& PersonAge(Person& p)  //用引用方式返回,会一直返回p2,值方式会创建新的临时对象
	{
		this->age += p.age;
		return *this;
	}
	int age;
};

void test1()
{
	Person p1(10);
	Person p2(20);
	//链式编程思想,每次都返回p2可实现
	p2.PersonAge(p1).PersonAge(p1).PersonAge(p1);  //值传递无法实现
}
int main()
{
	test1();
	return 0;
}

PS:
p2.PersonAge(p1).PersonAge(p1).PersonAge(p1);这样的连续追加的链式编程思想与cout << "a=" << a << endl;的本质相同
实现需要每次返回值都为本身(p2/cout)

3.空指针访问成员函数

空指针也可调用成员函数,但要注意有没有用到this指针,如果用到this指针,通常要加以判断保证代码健壮性。

class Person
{
public:
	void getage()
	{
		if (this ==NULL)  //防止指针为空
			return;
	//m_age 即 this->m_age 传入指针不能为空
		cout << "age:" << m_age << endl;
	}
	int m_age;
};
void test2()
{
	Person* p = NULL;
	p->getage();//报错,因为传入空指针,无法访问属性m_age()
}
int main()
{
	//test1();
	return 0;
}

4.const修饰成员函数

成员函数后加const修饰后称常函数,常函数内不可修改成员属性,成员属性声明时加关键字mutable后,在常函数中依然可以修改,在常对象下也可以修改

声明对象前加const称为常对象,常对象只能调用常函数(普通成员函数可能修改属性)

class Person
{
public:
	void getage() const
	{
		this->m_age=10;  //加const不可修改
		cout << "age:" << m_age << endl;
	}
	int m_age;
	//mutable int m_age;  //加mutable修饰则可修改
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值