C++类的学习(进阶篇)

类的学习基础篇

类的学习基础篇

类的学习进阶篇

首先来看一个实例

实例一

Mystring.h

#ifndef MYSTRING_H_
#define MYSTRING_H_

#include <iostream>
#include <cstring>
using std::cout;
using std::endl;

class Mystring
{
	private:
		char *m_str;	// 字符串
		int m_len;		// 表示字符串长度
		static int m_count;	// 静态成员变量,统计对象个数
		const static int CINLEN = 80;	// 输入的最大长度
	public:
		// 构造函数
		Mystring();		// 默认构造函数
		Mystring(const char *str);	// 构造函数
		Mystring(const Mystring &mystr);	// 复制构造函数

		// 析构函数
		~Mystring();	// 析构函数

		// 其他成员函数
		int length() const;

		// 运算符重载方法
		Mystring& operator=(const Mystring &mystr);	// 赋值运算符
		Mystring& operator=(const char *str);	// 赋值运算符
		char & operator[](int i);	// 重载中括号访问字符串,可读写对象
		const char & operator[](int i) const;	// 重载中括号访问字符串,只读

		// 静态成员函数
		static int get_count();		// 静态成员函数,返回统计的对象个数

		// 运算符重载的方法并作为类的友元
		friend std::ostream& operator<<(std::ostream &os, const Mystring& mystr);	// 输出
		friend bool operator<(const Mystring & mystr1, const Mystring & mystr2);	// 小于
		friend bool operator>(const Mystring & mystr1, const Mystring & mystr2);	// 大于
		friend bool operator==(const Mystring & mystr1, const Mystring & mystr2);	// 等于
		friend std::istream& operator>>(std::istream &is, Mystring& mystr);			// 输入
};
#endif

Mystring.cpp

#include "Mystring.h"
int Mystring::m_count = 0;

Mystring::Mystring()
{
	m_count++;
	cout << "调用构造函数,目前对象个数: " << m_count << endl;
	m_len = 0;
	m_str = new char[m_len + 1];
	m_str[0] = '\0';
	
}
Mystring::Mystring(const char *str)
{
	m_count++;
	cout << "调用构造函数,目前对象个数: " << m_count << endl;
	m_len = strlen(str);
	m_str = new char[m_len + 1];
	strcpy(m_str, str);
	
}
Mystring::~Mystring()
{
	m_count--;
	cout << "调用析构函数,目前对象个数: " << m_count << endl;
	delete [] m_str;;
	m_str = nullptr;
	
}
Mystring::Mystring(const Mystring &mystr)
{
	m_count++;
	cout << "调用复制构造函数,目前对象个数: " << m_count << endl;
	m_len = mystr.m_len;
	m_str = new char[m_len + 1];
	strcpy(m_str, mystr.m_str);
}

int Mystring::length() const
{
	return m_len;
}
Mystring& Mystring::operator=(const Mystring &mystr)
{
	if(this == &mystr)	// 如果赋值的对象是自己
	{
		return *this;
	}

	delete [] m_str;
	cout << "调用赋值运算符,目前对象个数: " << m_count << endl;
	m_len = mystr.m_len;
	m_str = new char[m_len + 1];
	strcpy(m_str, mystr.m_str);
	return *this;
}
// 定义一个接受常规字符串的赋值运算符,在调用时传入常规字符串,可以省去接受类对象的函数所产生的临时对象,以提高处理效率。
Mystring& Mystring::operator=(const char *str)
{
	delete [] m_str;
	cout << "调用赋值运算符,目前对象个数: " << m_count << endl;
	m_len = strlen(str);
	m_str = new char[m_len + 1];
	strcpy(m_str, str);
	return *this;

}
std::ostream& operator<<(std::ostream &os, const Mystring& mystr)
{
	os << mystr.m_str;
	return os;
}

std::istream& operator>>(std::istream &is, Mystring& mystr)
{
	char temp[Mystring::CINLEN];
	is.get(temp, Mystring::CINLEN);
	if(is)
	{
		mystr = temp;
	}
	while(is && is.get() != '\n');
	return is;
}

bool operator<(const Mystring & mystr1, const Mystring & mystr2)
{
	if(strcmp(mystr1.m_str, mystr2.m_str) < 0)
	{
		return true;
	}
	else
	{
		return false;
	}
}
bool operator>(const Mystring & mystr1, const Mystring & mystr2)
{
	return mystr2 < mystr1;
}
bool operator==(const Mystring & mystr1, const Mystring & mystr2)
{
	return (strcmp(mystr1.m_str, mystr2.m_str) == 0);
}

char & Mystring::operator[](int i)
{
	return m_str[i];
}
const char & Mystring::operator[](int i) const
{
	return m_str[i];
}

int Mystring::get_count()
{
	return m_count;
}

main.cpp

#include "Mystring.h"

// 按引用传参
void print_string1(const Mystring& mystr);

// 按值传参
void print_string2(const Mystring mystr);

// 按值返回对象
Mystring return_string_by_value(const Mystring& mystr);

int main()
{
    Mystring str1("hello world");
    cout << str1 << endl;
    cout << str1.length() << endl;
    std::cin >> str1;
    cout << str1 << endl;
    return 0;
}

void print_string1(const Mystring& mystr)
{
	cout << "按引用穿参: ";
	cout << mystr << endl;
}

void print_string2(const Mystring mystr)
{
	cout << "按值传参: ";
	cout << mystr << endl;
}
Mystring return_string_by_value(const Mystring& mystr)
{
	cout << "按值返回对象" << endl;
	return mystr;
}

静态成员

静态成员变量

  1. 无论创建了多少对象,程序都只创建一个静态变量副本。
  2. 不能在类声明中初始化静态成员变量(静态变量为const除外),因为声明描述了如何分配内存,但并不分配内存。
  3. 对于静态类成员变量,可以在类声明之外使用单独的语句来进行初始化,因为静态类成员是单独存储的,而不是对象的组成部分。

静态成员函数

  1. 不能通过对象调用静态成员函数,可以使用类名和作用域解析运算符来调用。
  2. 静态成员函数只能使用静态成员变量,因为静态成员函数不与特定的对象相关联。
class Mystring
{
    ...
    private:
        // 声明静态变量
        static int m_count;
        const static int CINLEN = 80;   // const静态变量可以在类声明中初始化
    public:
        static int get_count();		// 静态成员函数,返回统计的对象个数
    ...
};

// 初始化静态变量
int Mystring::m_count = 0;

// 定义
int Mystring::get_count()
{
	return m_count;     // 静态成员函数只能访问静态成员变量
}

// 调用
int count = Mystring::get_count(); // 使用类名和作用域解析运算符来调用静态成员函数

复制构造函数

复制构造函接受一个指向类对象的常量引用作为参数,用于将一个对象复制到新创建的对象中。

原型如下:

Class name(const Class_name &);

例如

Mystring(const Mystring &mystr);

何时调用复制构造函数

  1. 新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。
Mystring str1("hello world");
Mystring str2(str1);    // 调用复制构造函数
Mystring str3 = str1;   // 调用复制构造函数
Mystring str4 = Mystring(str1);         // 调用复制构造函数
Mystring *str5 = new Mstring(str1);     // 调用复制构造函数
  1. 每当程序生成了对象副本时,编译器都将使用复制构造函数。
    • 当函数按值传对象时,将会使用复制构造函数产生临时对象。
    void print_string2(const Mystring mystr);   // 按值传对象
    
    • 当函数按值返回对象时,将会使用复制构造函数产生临时对象。
    Mystring return_string3_by_value(const Mystring& mystr);    // 按值返回对象
    

默认复制构造函数

如果类中没有提供复制构造函数,C++会自动提供一个默认复制构造函数。

默认复制构造函数会逐个复制非静态成员,也就是浅拷贝。

对于Mystring这个类来说,默认复制构造函数实现如下:

Mystring(const Mystring &mystr)
{
    m_len = mystr.m_len;
    m_str = mystr.m_str;
}

如果使用默认复制构造函数

Mystring str1("hello world");
Mystring str2 = str1;   // 使用默认复制构造函数

首先,m_count这个静态变量不会增加。

其次,str1中的m_str和str2中的m_str都指向相同的地址,当程序结束调用析构函数时,会将同一块内存释放两次,从而可能导致程序崩溃。

注意

当类中存在需要使用new来分配内存的变量,一定要定义一个显示复制构造函数,来解决默认复制构造函数中存在的问题。

Mystring::Mystring(const Mystring &mystr)
{
	m_count++;
	cout << "调用复制构造函数,目前对象个数: " << m_count << endl;
	m_len = mystr.m_len;
	m_str = new char[m_len + 1];    // 深拷贝
	strcpy(m_str, mystr.m_str);
}

赋值运算符

当一个已初始化好的对象赋值给另一个已初始化好的对象,将会调用赋值运算符。

如果没有显示重写赋值运算符,C++将会提供一个默认的赋值运算符。

Class name & operator=(const Class_name &);

接受并返回一个指向类对象的引用。

默认赋值运算符也是浅拷贝,所以也可能导致默认复制构造函数一样的问题。

重写赋值运算符

Mystring& operator=(const Mystring &mystr); // 接受一个Mystring的对象
Mystring& operator=(const char *str);       // 接受常规字符串作为参数

Mystring& Mystring::operator=(const Mystring &mystr)
{
	if(this == &mystr)	// 如果赋值的对象是自己
	{
		return *this;
	}
	
	delete [] m_str;
	cout << "调用赋值运算符,目前对象个数: " << m_count << endl;
	m_len = mystr.m_len;
	m_str = new char[m_len + 1];
	strcpy(m_str, mystr.m_str);
	return *this;
}

// 定义一个接受常规字符串的赋值运算符,在调用时传入常规字符串,可以省去接受类对象的函数所产生的临时对象,以提高处理效率。
Mystring& Mystring::operator=(const char *str)
{
	delete [] m_str;
	cout << "调用赋值运算符,目前对象个数: " << m_count << endl;
	m_len = strlen(str);
	m_str = new char[m_len + 1];
	strcpy(m_str, str);
	return *this;
}
  1. 先判断是否是自我赋值;
  2. 再释放掉原来已初始化好的内存;
  3. 再进行深拷贝;
  4. 最后返回重新拷贝好的本对象引用。

new和delete的配对使用

在构造函数中使用new来分配内存时,必须在相应的析构函数中使用delete来释放内存。如果使用new[]来分配内存,则应使用delete[]来释放内存。

比较成员函数

friend bool operator<(const Mystring & mystr1, const Mystring & mystr2);	// 小于
friend bool operator>(const Mystring & mystr1, const Mystring & mystr2);	// 大于
friend bool operator==(const Mystring & mystr1, const Mystring & mystr2);	// 等于

将比较成员函数作为友元函数,可以使Mystring对象直接和常规字符串进行比较。

Mystring str("hello world");
if(str == "hello world")
{
    // ...
}
if("hello world" == str)
{
    // ...
}

使用中括号表示法访问字符

为了使Mystring可以像使用标准C风格字符串一样通过中括号来获取字符,可以重载中括号

char & operator[](int i);	// 重载中括号访问字符串,可读写对象
const char & operator[](int i) const;	// 重载中括号访问字符串,只读

char & Mystring::operator[](int i)
{
	return m_str[i];
}
const char & Mystring::operator[](int i) const
{
	return m_str[i];
}

Mystring str1 = "who";
str[0] = 'h';   // 读写,调用 char & operator[](int i);
const Mystring str2 = "who";
char a = str2[0];   // 只读,调用 const char & operator[](int i) const;

成员初始化列表

从概念上说,调用构造函数时,对象将在执行函数体中的代码之前被创建,而变量则在函数体中进行初始化。

但使用初始化成员列表,可以使变量在对象创建的时候被初始化。

#include <iostream>
using std::cout;
using std::endl;

class Test
{
    private:
        int m_data1;    // 普通变量
        const int m_data2;    // 非静态const数据成员
        int & m_data3;  // 引用的数据成员
    public:
        Test(int data1, int data2, int data3);

};
int main()
{
    Test t(1,2,3);
    return 0;
}

// 使用初始化成员列表
Test::Test(int data1, int data2, int data3)
    :m_data2(data2), m_data3(data3)     // 将在对象创建时进行初始化
{
    m_data1 = data1;    // 将在对象创建后进行初始化
}

注意

  1. 成员初始化列表只能用于构造函数;
  2. 必须用这种格式来初始化非静态const数据成员
  3. 必须用这种格式来初始化引用的数据成员
  4. 数据成员被初始化的顺序与它们出现在类声明中的顺序相同,与成员初始化列表排序顺序无关
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值