【C++学习记录】类

类的成员函数可以在类外定义,方便代码共享。
例如,要从一个文件中(设为A.cpp)使用另一个文件(设为B.cpp)中定义的类(设为Entity)。

方法一、将该B.cpp中的Entity类复制到A.cpp中,且Entity类的所有成员函数,包括构造函数、析构函数都得在类外定义,Entity类中只有类成员的声明。
这样就与函数的声明与定义分离一样,在A.cpp中使用Entity类成员函数,编译器在本文件中识别到该函数的声明,然后在链接阶段到其他文件中寻找函数定义。
补充:

//A.cpp
	class Entity
	{
		int a;
	public:
		void sayHello();
		void getA()
		{
			cout << a << endl;
		}
	};
	void Entity::sayHello()
	{
		cout << "Hello" << endl;
	}
//B.cpp
class Entity
{
	int b;
public:
	void sayHello();
	void getB()
	{
		cout << b << endl;
	}
};

在B.cpp中调用sayHello()成员函数会调用A.cpp文件中的定义的同名函数,但在B.cpp中实例化的Entity类没有成员变量a和成员函数getA()。
在B.cpp中定义sayHello()成员函数会链接错误“函数重定义”。

方法二、将类的声明写在.h文件中,成员函数的定义写在同名的cpp中。要使用该类时将头文件include。

类的内存分配
(来源:https://www.cnblogs.com/rednodel/p/9300729.html
一个空类class A{};占用一个字节,是为了区分空对象占内存的位置。一旦类有了成员变量后,调用sizeof获得的类的空间大小就等于类的所有非静态成员变量大小的总和。类的成员函数存放在代码区。
类对象实例化时在堆区开辟的内存只用于存放非静态成员变量or常量。所谓的“某对象的函数”,如Entity e; e.func();只是从逻辑角度而言,调用了“对象e的成员函数func()”。实际上所有Entity类生成的实例都“共享”func()成员函数。成员函数不论在类内外定义,或者被声明为inline函数都遵循这一规则。
静态/非静态成员函数都存在代码区,但非静态成员函数不能直接调用,是因为非静态成员函数内置了一个指向类对象的指针型参数(*this),非静态成员变量用该指针区分不同的对象。

class Entity
	{
		int a;
	public:
		void f1()
		{
		cout << "e.f1()"<< endl;
		}
		static void f2()
		{
			cout << "e.f2()" << endl;
		}
	};

	int main()
	{
		Entity *e = nullptr;
		e->f1();
		e->f2();
		return 1;
	}

都能正常运行,但如果f1()涉及到Entity的成员变量,就会发生运行错误。

补充:可以在f1()中加入一个判断条件避免运行错误:

	if (this == nullptr)
		return;

关于向前引用声明
教材中提到:“当一个类(类B)的对象作为另一个类(类A)的成员时,类B必须在类A后定义,这时就需要进行向前引用声明”。随后给出下列代码

class B;
class A
{
public:
	void f(B b);
};
class B
{
	void g(A a);
};

VS中编译通过。但是:如果在类A中尝试定义函数f(),就会发生编译错误“使用了未定义类型B”,在类A中声明成员变量B b;也会报同样错误;
而在类A中声明指针类型或引用类型的成员变量 B* b1; B& b2; ,编译通过。

刚接触到类犯的错误:
设有一个Entity类,语句“Entity e();”
不是用Entity类的默认构造函数实例化一个名为e的Entity类对象
而是“声明了一个函数名为e,返回类型为Entity类,不接收参数的函数”。

析构函数
主要用于释放类中申请的堆区内存

关于构造函数的调用
(来自https://www.bilibili.com/video/BV1et411b73Z?p=107
视频中提到显式法调用有参数的构造函数

Entity e1 = Entity(10);
Entity e2 = Entity(e1);

刚开始以为“=”右侧先创建了一个匿名对象,再复制给左侧用默认构造函数创建的e1、e2对象。但经过验证,以上两行代码的效果等价于下方代码,并没有调用复制构造函数。

Entity e1(10);
Entity e2(e1);

实际生成了匿名对象的代码:

Entity e;
e = Entity(50);

对象e由默认构造函数生成,第二行代码创建了一个匿名对象,并赋值给e,随后匿名对象析构。等价于下方代码。

Entity temp(50);
e = temp;
//释放temp 
Entity(10);

单独列出会创建一个匿名函数,在该行代码结束后立即销毁。仅适用于有参构造函数。

Entity e1;
Entity(e1);

编译器会报错“重定义”

隐式转换法

Entity e1 = 1000;

Entity类中要有接受对应类型参数的构造函数,只适用于只接收一个参数的构造函数。

复制构造函数
形参必须是(const 类名 & 对象名),没有const修饰符会被视为“接收一个Entity类型参数的构造函数”。
属于同一个类的对象之间可以互相视作friend,所以复制构造参数可以访问传入的对象的私有成员。
复制构造函数的调用时机
1、使用一个类对象初始化另一个类对象时

Entity e1;
Entity e2 = e1;

注意:Entity e2; e2 = e1; 是赋值(operator=),不会调用复制构造函数。
2、以传值的方式传入函数时
void f(Entity e){}
函数中的形参e未初始化,所以会调用复制构造函数,用传入函数的实参初始化形参e。
3、作为返回值时

Entity f1(Entity & e)
{
	return e;
}

Entity f2(Entity & e)
{
	Entity temp;
	temp = e;
	return temp;
}

调用f1()、f2()都只会调用一次复制构造函数,是在函数执行完毕即将return的时候,函数中实例化的类成员都得销毁,如f2()中的“temp”,所以得用一个匿名对象保存函数的返回值。复制构造函数用于初始化该匿名对象。
f1()会调用一次析构函数(针对匿名对象),f2()会调用二次析构函数(针对对象temp和匿名对象)。先调用复制构造函数,再调用析构函数。

this指针
功能:
1、消除类成员函数的形参与类成员变量重名的矛盾。类的成员函数中,对于类的成员函数都隐含使用了this指针。下方代码

class Entity
{
	int a;
public:
	void f(int a)
	{
		a = a;
	}
}

等价于

class Entity
{
	int a;
public:
	void f(int a)
	{
		this->a = a;
	}
}

如果成员函数中的形参与类成员变量重名,且形参在该函数中作为左值(例如形参以引用方式传入时可能会作为左值参与运算),编译器虽然不会报错,但为了达成目的,就得对类成员变量显式使用this指针。

//传入形参a接收函数f()的计算结果,函数f()返回bool类型用于做判断
bool f(int& a)
{
	a = this->a;
	return true;
}

2、 用于链式编程

Entity& f1()
{
	return *this; 
}
Entity* f2()
{
	return this; 
}

可以以这种方式调用f()函数

Entity e;
e.f1().f2()->f1().f1().f2()->f2();

成员函数f()运行结束后返回的还是调用该函数的对象自身,上方的代码中因为f1()、f2()没有进行任何操作,所以e.f1()等价于e。

3、书中的一个示例:避免对自身赋值

void A::copy(A& aa)
{
	if (this == &aa)
		return;
	*this = aa;
}

静态成员变量
同一个类的所有实例化对象都共享相同的静态成员变量。
静态成员变量必须初始化,初始化时前面不加static,避免与一般静态变量或对象混淆。
在Entity类内:
static int a;
在类外初始化:
int Entity::a = 10;
补充:被static修饰的变量范围仅限于该cpp文件,其他文件不可见,所以也无法用extern声明另一个文件中的静态变量
未初始化的静态成员变量会导致编译错误(无法解析的外部符号),但编译器无法定位到错误位置。

静态成员函数
疑问:既然普通成员函数也能访问静态成员变量,为什么还需要静态成员函数?
(来源:https://www.zhihu.com/question/34018632
答:访问静态成员变量的同时避免对类对象实例化,以此节省时间和空间。
所以私有的静态成员变量一般通过静态成员函数访问。
可以在类内定义或类外定义。在类外定义时不能使用static关键字。
静态成员函数不属于任何一个对象,所以也没有隐含this指针。
静态成员函数可以直接访问静态成员变量,若想访问非静态成员变量,必须通过参数传递的方式获得相应对象。

void Entity::show(Entity& e)//sta为Entity类的静态成员变量
{
	cout << e.a << endl;
	cout << sta << endl;
}

在main()中调用

Entity e;
Entity::show(e);

补充:私有静态成员函数无法直接访问,为什么要将静态成员函数声明为私有类型?
(来源https://bbs.youkuaiyun.com/topics/395659923?page=1
总结:实际项目中应该尽量把函数细化,使每个函数成为一个简洁、明确的“动作”。所以对于一个功能较杂的静态函数,可以按功能将其拆分为多个子函数,将不想让用户调用的部分设为私有类型。

常成员
常对象

const Entity ce;或 Entity const ce;

常成员函数
const修饰函数时只能用于类的成员函数。
函数声明和定义中都要有const。
const成员函数可参与重载,即void f();与void f() const;可同时定义。编译器根据调用的对象是否为常成员来区分。即:若条件允许,常对象调用常版本函数;一般对象调用普通版本函数。如果只有常成员函数,则一般对象也能调用该常成员函数。

void f()const		//mutable int sta;
{
	cout << sta++ << endl;
}

被mutable修饰的变量可以在const函数中作修改。

友元
可以访问类的私有成员的函数或其他类
有三种实现方法
1、 全局函数
2、 类
3、 成员函数

全局函数:
将函数的声明复制到类中,并在声明前加上“friend”关键字修饰。(友元不受其所在类的声明区域public private 和protected 的影响)
除非只访问类的私有静态成员,否则作为友元的全局函数通常接收一个相应的类类型参数。

类:
在A类中声明friend class B; B类即可访问A类的私有成员。
被声明为友元的类通常使用自己的成员函数访问其他类的私有成员,所以使用规则方法与全局函数一样。

成员函数:

friend void B::func(const A &a);

相比于全局函数多了一个类命名空间。使用方法同上。

友元类不可传递、不可继承、是单向的。设前提:A类声明B类为友元
不可传递: B类声明C类为友元,C类不是A类的友元;
不可继承: B类的子类不是A类的友元;
单向:A类不是B类的友元,不可访问B类私有成员。

对象数组

Entity eArray[5] = { Entity(1) ,Entity(2) ,Entity(3) };

eArray[]中有5Entity对象,前3个使用带参数的构造函数,后2个使用默认构造函数。

成员对象(一个类(设为B)作为另一个类(设为A)的成员)
类(A类)中必须有对成员对象的初始化,如果成员对象所属类(B类)没有默认构造函数,则必须在B类的构造函数中显式初始化成员对象;如果成员对象有默认构造函数,在A类实例化时会自动初始化成员变量。
成员变量按声明顺序初始化。成员变量初始化完成后,才执行A类构造函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值