从C到C++(2)-- 类和对象基础

很久以前学过C++,但是后来做嵌入式一直用C,面向对象什么的都快忘光了,写一些东西权当复习C++了


一、类和对象相关概念

(1)面向过程的程序设计

  1. 面向过程的程序 = 算法+数据结构
  2. 程序由全局变量函数构成,用函数操作数据结构
  3. 不足:
    1. 函数和其操作的数据结构没有直观的联系,程序长了之后,难以直接看出
      1. 某个数据结构有哪些函数可以对它操作操作
      2. 某个函数到底是操作哪些数据结构的
      3. 任意两个函数间存在怎样的调用关系?
    2. 没有“封装”和“隐藏”的概念,要访问某个数据结构中的某个变量,可以直接访问。当变量定义变化时,就必须找到所有调用处修改,难以检查哪个函数导致错误
    3. 难以进行代码重用

(2)面向对象的程序设计

  1. 面向对象的程序 = 类+类+…+类

  2. 设计方法:

    1. 把某类事物的客观属性(共同特点)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性)
    2. 把这类事物能进行的行为也归纳出来,形成一个个函数,用于操作数据结构(这一步叫抽象
    3. 然后通过某种语法形式,把数据结构和操作此结构的函数捆绑到一起,形成一个“类”,从而将数据结构和函数紧密联系起来(这步叫封装
  3. 基本特点:抽象、封装、继承、多态

  4. 类用class定义,和结构体形式基本一样,用类定义的变量叫对象

  5. 对象的内存分配:对象占据的内存大小,等于所有成员变量大小(不包含成员函数)之和

  6. 对象间的运算:可以用=进行赋值,但是不能用==,!=,>,<,>=,<=这些,除非经过运算符重载

  7. 使用成员变量和成员函数:

    1. 类名.成员名
    2. 指针->成员名
    3. 引用名.成员名

二、构造函数相关概念

(1)类的定义和使用

  1. 类定义示例:如下程序所示
  2. 类外定义成员函数
    1. 格式:返回值类型 类名::函数名(参数){}
    2. 说明:类名::说明后面的函数是类的成员函数,一定要通过对象 / 对象指针 / 对象引用才能调用
  3. 类成员可被访问的范围
    1. private:只有本类内可访问(缺省是这个)
    2. public: 任何地方都可以访问
    3. protect:本类和子类中可以访问
  4. 类成员的可访问范围
    1. 类的成员函数内部,可以访问:
      (1). 当前对象的全部属性、函数
      (2). 同类其他对象的全部属性、函数

    2. 类的成员函数以外的地方,可以访问:
      (1)该类对象的公有成员

    3. 解释:设计私有成员的机制,叫隐藏,其目的在于强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,只要更改成员函数即可,否则所有直接访问成员变量的语句都要修改

  5. 成员函数也可以重载或增加缺省参数,规则同普函数,注意避免二义性,比如有重载函数 void setValue();void setValue(int val=0); 调用时写 setValue();就会出错

示例程序1

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

class student
{
private:						//私有成员
	int age;
	string num;
public:							//公有成员
	void setAge(int age_)		//类内定义成员函数
	{age=age_;}
	
	int getAge();
	void setNum(string num_);
	string getNum();
	int getStuAge(student s); 
}; 


int student::getAge()			//类外定义成员函数
{
	return age;
}

void student::setNum(string num_)
{
	num=num_;
}

string student::getNum()
{
	return num;
}

//类的成员函数可以直接访问作为其参数的同类型对象的私有成员 
int student::getStuAge(student s)
{
	return s.age;
}

int main()
{
	student stu1,stu2;
	stu1.setAge(21);
	stu1.setNum("011610615");
	cout<<"stu1年龄:"<<stu1.getAge()<<endl;
	cout<<"stu1学号:"<<stu1.getNum()<<endl; 
	
	stu2.setAge(20);
	cout<<"stu2年龄:"<<stu1.getStuAge(stu2)<<endl;
} 

(2)构造函数初探

  1. 基本概念

    1. 是成员函数的一种
    2. 名字与类名相同,可以有参数,不能有返回值(void也不行)
    3. 作用是对对象进行初始化,如给成员函数赋值
    4. 如果定义类时没显示写出构造函数,编译器自动生成一个默认的无参数构造函数,这个函数不做任何操作
    5. 对象生成时构造函数自动调用。对象生成后不能再调用了
    6. 一个类可以有多个构造函数,要求参数个数或参数类型不同(类似重载)
  2. 为什么需要构造函数

    1. 构造函数执行必要的初始化工作,有了构造函数,就不必再专门写初始化函数,也不用忘记担心忘记调用初始化函数
    2. 有时对象没有初始化就被使用,会导致程序错误
  3. 构造函数示例

    1. 上面那个程序,没人工定义构造函数,是用自动生成的构造函数初始化的
    2. 下面的程序显示定义了构造函数
  4. 对象数组的构造函数调用情况

(1) 假设类student有构造函数student()student(int n),下面定义对象数组
		1. student array[2];				//没指明参数,默认用无参的,两个对象都用 student()构造
		2. student array[2]={3,4}; 			//array[0]用 student(3)构造 ,array[1]用 student(4)构造  
		3. student array[2]={3}; 			//array[0]用 student(3)构造 ,array[1]用 student()构造  
		4. student *array=new student[2]; 	//两个对象都用 student()构造
(2) 假设类test有构造函数 test(int a); test(int a,int b);test(); 三个构造函数
		1. test array[3]={1,test(1,2)};					//array[0]用 test(1); array[1]用 test(1,2); array[2]用 test();
		2. test *pArray[3]								//没有构造函数被调用,因为这里是指针,没有生成实际的对象
		3. test *pArray[3]={new test(4),new test(1,2)};	//*pArray[0]用 test(4); *pArray[1]用 test(1,2); pArray[2]指向的对象没有生成,不调用 

示例程序2

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

class student
{
private:
	int age;
	string num;
public:
	student(string num_,int age_=20);
	int getAge()		{return age;}
	string getNum()		{return num;}
}; 

//注意函数缺省值只出现在声明中 
student::student(string num_,int age_)
{
	num=num_;
	age=age_;
}

int main()
{
//	student stu;						//报错,没有参数 
	student stu1("123456");				//构造函数使用缺省参数 
	student *stu3=new student("111111");
	student stu2("654321",21);			//构造函数不使用缺省参数 
	
	
	cout<<"stu1年龄:"<<stu1.getAge()<<endl;
	cout<<"stu1学号:"<<stu1.getNum()<<endl;
	cout<<"stu2年龄:"<<stu2.getAge()<<endl;
	cout<<"stu2学号:"<<stu2.getNum()<<endl;   
	cout<<"stu3年龄:"<<stu3->getAge()<<endl;
	cout<<"stu3学号:"<<stu3->getNum()<<endl; 
	
	delete stu3; 
}

(3)拷贝构造函数

  1. 基本概念
    1. 只有一个参数,即同类对象的引用
    2. 形如 X::X(X &) 或 X::X(const X &),二者选一,后者可以用常量对象作为参数
    3. 如果没有定义复制构造函数,那么编译器生成默认的复制构造函数,这个函数可以完成两个对象间的复制功能
  2. 注意
    1. 一个类只能有一个复制构造函数
    2. 拷贝构造函数参数必须是对象的引用
  3. 复制构造函数起作用的三种情况
    1. 用一个对象初始化另一个对象,如:
      student stu1(stu2);
      student stu1 = stu2;
    2. 如果某函数有一个参数为类A的对象,此函数被调用时,类A的复制构造函数被调用,用于初始化局部形参对象
      注意:某函数有一个参数为类A的引用,此时没有局部对象被构造,所以不会调用复制构造函数
    3. 如果函数的返回值是类A的对象,则函数返回时会调用A的复制构造函数生成临时对象,复制构造函数的参数是return的那个对象
      注意:对象间的赋值(如stu1=stu2),不调用复制构造函数
  4. 常量引用参数的使用
    1. 上面提到了 :有一个参数为类A的对象的函数,调用时会导致复制构造函数的调用,开销大
      要减小开销,可以用类的引用类型作为参数
      如果希望确保实参的值在函数中不被改变,可以加上const关键字(修改会报错)

示例程序3

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

class student
{
private:
	int age;
	string num;
public:
	student(string num_="123456",int age_=20);
	student(student &stu);
	
	void IncreaseAge()	{age++;}
	int getAge()		{return age;}
	string getNum()		{return num;}
}; 

//注意这里不要写缺省值 
student::student(string num_,int age_)
{
	num=num_;
	age=age_;
	cout<<"调用构造函数"<<endl; 
}

student::student(student &stu)
{
	num=stu.num;
	age=stu.age;
	cout<<"调用拷贝构造函数"<<endl; 
}

//观察作为函数参数和返回值时拷贝构造函数的行为 
student IncreaseAge(student t)
{
	t.IncreaseAge();
	cout<<"stu1年龄增加!"<<endl;
	return t;	
} 

int main()
{
	student stu1;						//构造函数使用缺省参数 
	student stu2(stu1); 				//拷贝构造函数 
	
	cout<<"stu1年龄:"<<stu1.getAge()<<endl;
	cout<<"stu1学号:"<<stu1.getNum()<<endl;
	cout<<"stu2年龄:"<<stu2.getAge()<<endl;
	cout<<"stu2学号:"<<stu2.getNum()<<endl<<endl;   
	
	stu1=IncreaseAge(stu1);
	cout<<"stu1年龄:"<<stu1.getAge()<<endl;
}

(4)类型转换构造函数

  1. 功能:转换构造函数将一个指定的数据转换成类对象
  2. 只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作转换构造函数 。我们要在类中定义一个只有一个参数的构造函数,参数是待转换类型的数据,在函数体中指定转换的方法
  3. 当需要的时候,系统会自动调用转换构造函数,建立一个无名的临时对象(临时变量)
  4. 当出现对象重新赋值时,如下面的c1=9,会先用9作为参数调用类型转换构造函数得到临时对象,然后用此对象给c1赋值

示例程序4

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

class Complex
{
public:
	double real,imag;
	
	Complex(int i)
	{
		cout<<"调用转换构造函数"<<endl;
		real=i,imag=0; 
	}
	Complex(double r,double i)
	{
		cout<<"调用构造函数"<<endl;
		real=r,imag=i; 
	}
};

int main()
{
	Complex c1(7,8);
	Complex c2=12;	//这种初始化语句,等价于参数为12的构造函数 
	Complex c3(12);	//和上面那条等价	
	c1=9;			//先用9作为参数调用类型转换构造函数得到临时对象,然后用此对象给c1赋值
	cout<<c1.real<<","<<c1.imag<<endl;
	
	return 0; 
}

三、析构函数

  1. 什么是析构函数:名字与类名相同,在前面加~,没有参数和返回值,一个类最多有一个析构函数
  2. 调用时机:对象消亡时自动被调用,可以定义析构函数在对象消亡前做善后工作,比如释放内存空间等(对于非new出来的对象,析构函数不能释放空间)
  3. 如果没用写析构函数,系统会自己生成一个缺省的析构函数,什么都不做
  4. 常见的析构函数调用位置
    (1). 对象数组生命周期结束时,每一个元素的析构函数都被调用
    (2). 对于new出来的对象数组,delete的时候调用析构函数,注意别忘了加[],否则只delete一个对象
    (3). 函数形参对象、函数返回的临时对象,在消亡时也会调用析构函数

示例程序5

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

class String
{
private:
	char *p;
public:
	String()
	{
		cout<<"构造函数"<<endl; 
		p=new char[10]; 
	} 
	~String();
	
};

String::~String()
{
	cout<<"调用析构函数"<<endl; 
	delete[]p;
}

 
String copy(String s)
{
	return s;
}

int main()
{
	String str[5];			//调用构造函数5次 
	String *p=new String;	//调用构造函数 
	delete p; 				//析构 
	
	String str1=copy(str[1]);	//函数进入时,用拷贝构造函数生成形参对象。离开时形参析构,同时用拷贝构造函数生成临时对象返回,程序结束时这个临时对象析构 
	cout<<"end main"<<endl;	
	return 0;				//析构5次 
}

四、一个例子

  • 尝试分析以下程序中构造函数和析构函数的调用情况

示例程序6

#include<iostream>
using namespace std;

class Demo
{
	int id;
public:
	Demo(int i)
	{
		id=i;
		cout<<"id="<<id<<" 构造"<<endl; 
	} 
	~Demo()
	{
		cout<<"id="<<id<<" 析构"<<endl; 
	}
};

Demo d1(1);									//全局变量,构造 
void func()
{
	static Demo d2(2);						//构造,静态变量离开局部时不会析构 
	Demo d3(3);								//构造,离开局部时会析构  
	cout<<"func"<<endl; 
}

int main()
{
	Demo d4(4);								//变量初始化,构造 
	d4=6;									//转换构造函数,构造临时对象,给de赋值后临时对象析构 
	cout<<"main"<<endl;
	{
		Demo d5(5);							//局部变量,进入局部时构造,离开时析构 
	}
	func();									
	cout<<"main end"<<endl;
	return 0;								//d4 d2 d1依次析构 
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云端FFF

所有博文免费阅读,求打赏鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值