类的学习基础篇
类的学习进阶篇
首先来看一个实例
实例一
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;
}
静态成员
静态成员变量
- 无论创建了多少对象,程序都只创建一个静态变量副本。
- 不能在类声明中初始化静态成员变量(静态变量为const除外),因为声明描述了如何分配内存,但并不分配内存。
- 对于静态类成员变量,可以在类声明之外使用单独的语句来进行初始化,因为静态类成员是单独存储的,而不是对象的组成部分。
静态成员函数
- 不能通过对象调用静态成员函数,可以使用类名和作用域解析运算符来调用。
- 静态成员函数只能使用静态成员变量,因为静态成员函数不与特定的对象相关联。
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);
何时调用复制构造函数
- 新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。
Mystring str1("hello world");
Mystring str2(str1); // 调用复制构造函数
Mystring str3 = str1; // 调用复制构造函数
Mystring str4 = Mystring(str1); // 调用复制构造函数
Mystring *str5 = new Mstring(str1); // 调用复制构造函数
- 每当程序生成了对象副本时,编译器都将使用复制构造函数。
- 当函数按值传对象时,将会使用复制构造函数产生临时对象。
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;
}
- 先判断是否是自我赋值;
- 再释放掉原来已初始化好的内存;
- 再进行深拷贝;
- 最后返回重新拷贝好的本对象引用。
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; // 将在对象创建后进行初始化
}
注意
- 成员初始化列表只能用于构造函数;
- 必须用这种格式来初始化非静态const数据成员;
- 必须用这种格式来初始化引用的数据成员;
- 数据成员被初始化的顺序与它们出现在类声明中的顺序相同,与成员初始化列表排序顺序无关。