【C++笔记】类和对象

类的定义与实例化

C++中的类与C语言中的结构体相似,但是类有一个显著的特征就是里面不仅可以定义成员变量,还可以定义成员函数

定义

class Test
{
	int _val;
	void Init() // Init为类的成员函数
	{
		//....
	}
};

类的成员函数也可以做到声明与定义分离,不过函数定义时需要指定该函数的类域

class Test
{
	int _val;
	void Init(); // 函数声明
};

void Test::Init() // 函数的定义在类外实现
{
	//....
}

实例化

如果说类的定义相当于画建筑图纸,那么类的实例化就是在现实世界将这栋建筑完工。

class Test // 类的定义
{
	int _val;
};

Test T; // 类的实例化

类的定义并不会向操作系统申请空间,而实例化会。

类的访问限定符

类的三个访问限定符:publicprotectedprivate

  • public:可以被外部程序访问。
  • protected:可以被子类访问,但不能被其他不相干的类访问。
  • private:只能在该类被访问,可以被内部类访问,但不能被子类访问。

在类中如果不指定默认就是私有成员,而结构体默认公有。

class Test
{
private:
	int _val1;
public:
	void Init()
	{
		//....
	}
};

访问限定符是类进行封装的一个手段。
面向对象有三大特性:封装、继承、多态。

类和对象主要研究的就是“封装”。即:将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。如果想要访问对象的内部信息,必须通过严格的接口或方法来实现。

对象在类中的存储

对于类中的成员变量,存储方式和结构体是相同的,遵循内存对齐原则。
而对于成员函数,是放在代码段上,受访问作用域限定。

class Test
{
private:
	int _val1;
public:
	void Init()
	{
		//....
	}
};

这个Test类的大小是4个字节。也就是说,计算类的大小时并不会算上成员函数。

类的成员函数

this指针

class Test
{
private:
	int _val;
public:
	void Init(int val)
	{
		_val = val;
		// 等价于 this._val = val
	}
};
///
	Test T;
	T.Init(1);

以上的Init函数实际上有两个参数,一个是传入的val,另一个就是this指针。

成员函数通过this指针this指针来访问这个类中的变量。

我们可以在函数中使用this指针,但不能改变this指针的指向。

类的成员函数都有this指针,但static成员函数没有。

类的默认成员函数

在创建类的时候,有一些默认成员函数。本文着重于以下四个——构造函数、析构函数、拷贝构造、赋值重载

如果我们没有实现这些函数,编译器会帮我们自动实现。我们实现了编译器就不会在生成。

构造函数

构造函数的功能是帮我们初始化变量。

构造函数的函数名应当和类名相同,并且没有返回值(不是void)

class Test
{
private:
	int _val;
public:
	Test(int val = 0)// 自定义实现的构造函数(全缺省),编译器就不会再实现
	{
		_val = val;
	}
};

这段代码是正确的。

class Test
{
private:
	int _val;
public:
	Test(int val)// 自定义实现的构造函数,编译器就不会再实现
	{
		_val = val;
	}
};
///
Test T;

这段代码不能编译通过这个T对象没有合适的默认构造函数可用,因为它传入的参数和这个构造函数不对应。

Test T(1);

这个是对的,因为对于这个T对象来说,Test函数与它传入的参数相同,Test()就是合适的构造函数。

那编译器自动生成的默认构造函数能做什么呢?

  • 对于内置类型(int,char等)变量,编译器不做处理。
  • 对于自定义类型(自己定义的类、结构体等)变量,编译器调用它们的构造函数。
class Stu
{
private:
	char _name[20];
	char _id[10];
public:
	Stu()
	{
		cout << "Stu()" << endl;
	}
}

class Test
{
private:
	int _val;
	Stu _s;
};

int main()
{
	Test T;
	return 0;
}

以上代码的结果是输出一个Stu(),也就印证了编译器生成的构造函数会调用自定义类型的构造函数

初始化列表
class Test
{
private:
	int _val;
	int _num;
	int _code;
public:
	Test(int val = 0, int num = 0, int code = 0)
	:_val(val)
	,_num(num)
	,_code(code)
	{
		if(val < 0 || num < 0 || code < 0)// 函数体内可以检验数据合法性
			exit(-1);
	}
};
  • 初始化时先走初始化列表后进入函数体
  • 成员变量初始化顺序是按照对象声明顺序,与初始化列表顺序无关
  • 每个成员变量在初始化列表只出现一次
  • 引用成员变量、const成员变量必须走初始化列表(它们不能再次修改)
析构函数

析构函数用来完成对象销毁。

析构函数的函数名是类名前面加上一个~,没有返回值(不是void)

class Test
{
private:
	int _val;
public:
	~Test()// 自定义实现的析构函数,编译器就不会再实现
	{
		cout << "~Test()" << endl;
	}
};

和构造函数类似,我们实现了析构函数,编译器就不会在实现了。

编译器实现的析构函数和构造函数类似

  • 对于内置类型,不做处理
  • 对于自定义类型,则调用它们的析构函数。

值得一提的是,析构函数的调用顺序和构造函数是相反的。

储存在不同域的对象析构函数的调用顺序会发生变化:局部对象->局部静态对象->全局对象。

拷贝构造

拷贝构造函数用来完成将一个对象赋值给另外一个对象的工作。

拷贝构造函数名和类名一样,没有返回值,只接受被拷贝对象的引用作为参数。

class Test
{
private:
	int _val;
public:
	Test(Test& T)// 这里如果使用传值调用,会导致无限递归。
	{
		_val = T._val;
	}
};

如果我们没有实现拷贝构造函数,编译器会自动生成一个。

  • 编译器自动生成的拷贝构造函数完成对象的浅拷贝(拷贝值)。
  • 在需要深拷贝的场景,我们还是得自己实现。
赋值运算符重载

赋值运算符重载是运算符重载的一种。

什么是运算符重载?

我们日常用的 +、-、*、/ 等运算符都是运用于内置类型的,如果类想要运用这些运算符,那么就得进行运算符重载。

例如以Test类为例重载赋值运算符=

class Test
{
private:
	int _val;
public:
	Test& operator=(const Test& t)// 返回引用方便连续赋值,作为类的成员函数有this指针
	{
		_val = t._val;
		return *this;
	}
};

有5个运算符是不能够重载的:.*::sizeof?:.

C/C++中没有的符号不能进行重载。

如果我们没有实现赋值运算符重载,编译器会自动生成。

  • 对于内置类型,进行浅拷贝。
  • 对于自定义类型,调用它们的赋值运算符重载。

注意:运算符重载中的前置++和后置++

为了区别前置++和后置++

C++语法规定后置++重载有一个int类型的参数,但是使用时不需要手动传。

class Test
{
private:
	int _val;
public:
	Test& operator++()// 前置++(先++,再返回++后的引用)
	{
		_val += 1;
		return *this;
	}
		Test operator++(int)// 后置++(++后返回之前的值)
	{
		Test tmp(*this);
		_val += 1;
		return tmp;// 不返回引用是因为出作用域tmp要销毁
	}
};

const成员函数

本质上是对this指针的修饰,表示不能修改this指针指向的对象。

class Test
{
private:
	int _val;
public:
		Test operator++(int) const// 这里this._val就不能修改了
	{
		Test tmp(*this);
		_val += 1;
		return tmp;
	}
};

static成员

class Test
{
private:
	int _val;
	static int _num;
public:
	static int GetNum()
	{
		return _num;
	}
};

int Test::_num = 0;
  • static成员为该类所有对象共享,不属于任何对象,存放在静态区,但是受到访问限定符限制。
  • static成员一定要在类外定义和初始化。
  • static成员函数没有this指针,除了类中的static成员变量,不能访问其他任何变量

explicit关键字

class Test
{
private:
	int _val;
public:
	explicit Test(int val = 0)
	{
		_val = val;
	}
};

int main()
{
	Test T1 = 1; // 这一种赋值方式之所以可以是因为发生了隐式类型转换
	Test T2(1);
	return 0;
}

而加了explicit关键字,就禁止了构造函数的隐式类型转换,T1没有合适的构造函数会报错,T2不会。

友元

友元函数

友元函数相当于类给外面的函数开了个特权,能够让其访问内部私有或受保护的变量。

class Test
{
friend ostream& operator<<(ostream& out, const Test& T);
private:
	int _val;
};

ostream& operator<<(ostream& out, const Test& T)
{
	out << T。_val << endl;
	return out;
}

友元类

友元函数相当于类给外面的类开了个特权,能够让其访问内部私有或受保护的变量。

class Test
{
friend class A;
private:
	int _val;
};

class A
{
	int _num;
	Test a;
public:
	void GetVal()
	{
		cout << a._val << endl;
	};
};

内部类

一个类被定义在另一个的里面,就叫做内部类。

class A
{
	static int _val;
	class B // 注意与 “B _val;” 区分
	{
		int _num;
		void GetVal()
		{
			cout << _val <<endl;
		}
	};
};
  • 内部类是外部类的友元类。B可以访问A的变量
  • B可以直接访问A的static成员变量,不需要指定类域
  • 以上sizeof(A) == 4,A的大小不包括B
  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值