「C++学习笔记」类和对象初见

目录

一、面向对象

二、类的大小

三、特殊成员函数

四、构造函数

五、析构函数

六、复制构造函数(拷贝构造函数)

七、复制赋值运算符(赋值运算符重载)

八、运算符重载


一、面向对象

要论C和C++最本质的区别,那就是C是面向过程的语言,而C++是面向对象的语言了吧,虽然我现在不敢说完全掌握面向对象,不过可以说是略懂一二,所以说一下我的理解吧:我们把现实世界的物质运动进行抽象,得到了物理学,物理学中有质量、速度等概念,但现实世界中并没有质量、速度的实体,这是我们人类为了认识世界抽象出来的东西,同样,我们也可以把计算机世界的对象进行抽象,于是我们就得到了类,类中有各种成员,我们对类进行实例化,就可以在计算机世界得到对象!

二、类的大小

空类的大小为1,成员函数不参与计算,类的大小遵循内存对齐。

#include <iostream>
using namespace std;

//空类
class Empty
{};

//只有成员函数的类
class OnlyFunc
{
public:
	void Print1()
	{
		cout << "hello class" << endl;
	}

	void Print2()
	{
		cout << "hello violet" << endl;
	}
};

//既有成员变量,也有成员函数
class All
{
public:
	void Print()
	{
		cout << "hello class" << endl;
	}

private:
	char c;
	int i;
};

int main()
{
	cout << "sizeof(Empty) = " << sizeof(Empty) << endl;
	cout << "sizeof(OnlyFunc) = " << sizeof(OnlyFunc) << endl;
	cout << "sizeof(All) = " << sizeof(All) << endl;

	//输出为:
	//sizeof(Empty) = 1
	//sizeof(OnlyFunc) = 1
	//sizeof(All) = 8

	return 0;
}

三、特殊成员函数

四、构造函数

1.内置类型问题

内置类型(char、int、各类指针...)的成员变量不会调用编译器自动生成的构造函数,自定义类型(struct、class...)的成员变量才会调用编译器自动生成的构造函数。

#include <iostream>
using namespace std;

class X
{
public:
	X(int x = 0)
	{
		_x = x;
		cout << _x << endl;
	}

private:
	int _x;
};

class Y
{
public:
	void Print()
	{
		cout << _y << endl;
	}

private:
	int _y;//内置类型
	X _x;//自定义类型

    //C++11
    //int _y = 0;
};

int main()
{
	Y y;
	y.Print();
	return 0;
}

y.Print();会输出一个不能确定的值,因为y._y没有调用编译器自动生成的构造函数,也就没有初始化。为了解决这个问题,C++11中可以在内置类型的成员变量声明的时候给定一个默认值。

2.必须使用初始化列表初始化的成员变量

在不考虑C++11给定默认值的情况下,必须使用初始化列表初始化的成员变量有:const修饰的成员变量、引用成员变量。这些变量的特点是它们只有一次初始化的机会,那就是在它们定义的时候。

#include <iostream>
using namespace std;

class X
{
public:
	X(const char c, int& x)
        //初始化列表,以:开始,之后每个变量用,隔开
		:_c(c)
		, _x(x)
	{
		cout << _c << endl;
		cout << &_x << endl;
	}

private:
	const char _c;//const修饰的成员变量
	int& _x;//引用成员变量
};

int main()
{
	int temp = 0;
	X x('!', temp);
	cout << &temp << endl;
	return 0;
}

3.初始化列表的执行顺序

谁先声明谁先初始化,与初始化列表的顺序无关。

#include <iostream>
using namespace std;

class X
{
public:
	X()
        //谁先声明谁先初始化,与初始化列表的顺序无关
        //这里先初始化_a,再初始化_b
        //而初始化_a的时候_b还没被初始化,所以导致_a的值不能确定
		:_b(1)
		, _a(_b)
	{
		cout << _a << endl;
		cout << _b << endl;
	}

private:
	int _a;
	int _b;
};

int main()
{
	X x;
	return 0;
}

4.explicit禁止隐式类型转换

explicit:指定构造函数为显式,即它不能用于隐式转换和复制初始化。

#include <iostream>
using namespace std;

class X
{
public:
	//用explicit修饰构造函数即可禁止隐式类型转换
	//explicit X(int x = 0)
	X(int x = 0)
		:_x(x)
	{
		cout << "构造函数" << endl;
	}

	X(const X& x)
	{
		this->_x = x._x;
		cout << "拷贝构造" << endl;
	}

private:
	int _x;
};

int main()
{
	//构造函数
	X x1(1);

	//隐式类型转换,会先生成一个X类型的临时变量,再调用拷贝构造
	//经过编译器优化,会直接调用构造函数
	X x2 = 1;

	//证明临时变量存在
	//X& rx = 1;//会报错,因为临时变量具有常性
	const X& rx = 1;//不会报错,且这个临时变量会调用构造函数

	return 0;
}

C++官方文档对explicit的解释(explicit 说明符 - cppreference.com)​:

五、析构函数

 1.调用析构函数的时间点

作用域结束和使用delete表达式会调用析构函数,同一作用域,后实例化的对象先调用析构函数。

#include <iostream>

struct A
{
    int i;

    A(int num) : i(num)
    {
        std::cout << "构造 a" << i << '\n';
    }

    ~A()
    {
        std::cout << "析构 a" << i << '\n';
    }
};

A a0(0);

int main()
{
    A a1(1);
    A a2(2);
    A* p;

    { // 嵌套的作用域
        A a3(3);
        p = new A(4);
    } // a2 离开作用域

    delete p; // 调用 a3 的析构函数
    return 0;
}

//输出为:
//构造 a0
//构造 a1
//构造 a2
//构造 a3
//构造 a4
//析构 a3
//析构 a4
//析构 a2
//析构 a1
//析构 a0

2.内存泄漏

如果类中有申请内存,则一定要写析构函数,否则会造成内存泄漏,比如Stack类:

class Stack
{
public:
	Stack(int capacity = 8)
	{
		_arr = new int[capacity];
		_size = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		delete[] _arr;
		_arr = nullptr;
		_size = 0;
		_capacity = 0;
	}

private:
	int* _arr;
	int _size;
	int _capacity;
};

int main()
{
	Stack s;
	return 0;
}

六、复制构造函数(拷贝构造函数)

深拷贝与浅拷贝

编译器默认生成的复制构造函数只会进行浅拷贝,而像Stack类需要把堆上的空间也拷贝下来,则需要深拷贝。

#include <string.h>

class Stack
{
public:
	Stack(int capacity = 8)
	{
		_arr = new int[capacity];
		_size = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		delete[] _arr;
		_arr = nullptr;
		_size = 0;
		_capacity = 0;
	}
    
    //复制构造函数
	Stack(const Stack& stack)
	{
		//深拷贝
		_arr = new int[stack._capacity];
		memmove(_arr, stack._arr, stack._size * sizeof(int));

		//浅拷贝
		_size = stack._size;
		_capacity = stack._capacity;
	}

private:
	int* _arr;
	int _size;
	int _capacity;
};

int main()
{
	Stack s1;
	Stack s2(s1);
	return 0;
}

七、复制赋值运算符(赋值运算符重载)

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

class Stack
{
public:
	Stack(int capacity = 8)
	{
		_arr = new int[capacity];
		_size = 0;
		_capacity = capacity;
	}

	~Stack()
	{
		delete[] _arr;
		_arr = nullptr;
		_size = 0;
		_capacity = 0;
	}

	Stack(const Stack& stack)
	{
		//深拷贝
		_arr = new int[stack._capacity];
		memmove(_arr, stack._arr, stack._size * sizeof(int));

		//浅拷贝
		_size = stack._size;
		_capacity = stack._capacity;
	}

	void Push(int x)
	{
		//CheckCapacity();
		_arr[_size] = x;
		++_size;
	}

	void Print()
	{
		for (int i = 0; i < _size; ++i)
		{
			cout << _arr[i];
		}
		cout << endl;
	}

	Stack& operator=(const Stack& stack);

private:
	int* _arr;
	int _size;
	int _capacity;
};

Stack& Stack::operator=(const Stack& stack)
{
	if (this != &stack)//非自赋值
	{
		if (_capacity < stack._capacity)
		{
			cout << "须扩容" << endl;
		}
		else
		{
			memmove(_arr, stack._arr, stack._size * sizeof(int));
			_size = stack._size;
			_capacity = stack._capacity;
		}
	}
	return *this;
}


int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Print();

	Stack s2;
	s2 = s1;
	s2.Print();

	return 0;
}

八、运算符重载

1.限制

2.前置++与后置++

#include <iostream>
using namespace std;

class X
{
public:
	X(int x = 0) : _x(x) {};

	//前置++
	X& operator++()
	{
		++_x;
		return *this;
	}

	//后置++
	//int仅仅是为了和前置++区分,无实际意义
	X operator++(int)
	{
		X temp(*this);
		++_x;
		return temp;
	}

	void Print()
	{
		cout << _x << endl;
	}

private:
	int _x;
};

int main()
{
	X x1;
	++x1;
	x1.Print();
	X x2 = x1++;
	x1.Print();
	x2.Print();
	return 0;
}

//输出为:
//1
//2
//1

3.流插入与流提取

流插入与流提取运算符往往声明为非成员友元。

#include <iostream>
using namespace std;

class Date
{
	//友元
	friend ostream& operator<<(ostream& out, const Date& date);
	friend istream& operator>>(istream& in, Date& date);

public:
	Date(int year = 1, int month = 1, int day = 1) : _year(year), _month(month), _day(day)
	{}

private:
	int _year;
	int _month;
	int _day;
};

inline ostream& operator<<(ostream& out, const Date& date)
{
	out << date._year << "年" << date._month << "月" << date._day << "日" << endl;
	return out;
}

inline istream& operator>>(istream& in, Date& date)
{
	in >> date._year >> date._month >> date._day;
	return in;
}

int main()
{
	Date date(2023, 3, 9);
	cout << date;
	cin >> date;
	cout << date;
	return 0;
}

C++官方文档:cppreference.com

最后,分享一张好看的图片(´・ ᴗ ・`)

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值