对类和对象的理解

1. 类的实例化

类不占用空间,在使用的时候才发生类的实例化,才开辟了空间

 

  • 开辟了空间叫定义,没有开辟空间的叫声明
  • static 修饰的声明,只在当前文件可见,链接时没有放进符号表,所以两次打印的地址不同
  • extern 修饰的声明在链接时会放进符号表,所以两次打印的地址相同2类对象模型

2. 类对象的存储方式猜测

2.1 猜测一:对象中包含类的各个成员

  •  缺陷:每个对象中成员变量是不同的,都调用同一份函数,每次调用相同的代码就会保存一次,多个对象的话,相同代码保存多次,浪费空间

2.2 猜测二:代码只保存一份,在对象中保存存放代码的地址 

  • 这里在访问类成员函数的时候,是通过对象中保留了类成员函数表的指针,通过指针的解引用进行访问的,

2.3 猜测三:只保存成员变量,成员函数存放在公共的代码段

  •  编译链接时就根据函数名去公共代码区找到函数地址
  • C++类对象的存储方式是第三种,只保存成员变量,成员函数存放在公共的代码段

3. 类的大小

#include<iostream>
using namespace std;

class A1
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}

	void func()
	{
		cout << "void A::func()" << endl;
	}
private:
	char _a;
	int _i;
};

class A2 {
public:
	void f2() {}
};

class A3{

};

int main()
{
	cout << sizeof(A1) << endl;
	cout << sizeof(A2) << endl;
	cout << sizeof(A3) << endl;
	return 0;
}

  •  一个类的大小,实际就是该类中”成员变量”之和,这里和C语言一样也会发生内存对齐
  • 空类的大小:编译器给了空类一个字节来唯一标识这个类的对象。

4. 成员变量命名规则的建议

驼峰法——单词和单词之间首字母大写间隔

class Date
{
public:
     void Init(int year)
     {
     _year = year;
     }
private:
     int _year;
};
  • 函数名、类名等所有单词首字母大写 DateMgr
  • 变量首字母小写,后面单词首字母大写 dateMgr
  • 成员变量,首单词前面加_   _dateMgr

5. this指针

#include<iostream>
using namespace std;
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;     // 年   -> 声明
	int _month;    // 月
	int _day;      // 日
};
	
int main()
{
	Date d1;
	d1.Init(2022, 7, 17);
	
	Date d2;
	d2.Init(2022, 7, 18);
	
	d1.Print();
	d2.Print();
	
	return 0;
}
  • Date类中有 Init Print 两个成员函数,他们都是放在公共代码区中的,而函数体中没有关于不同对象的区分,那当d1调用 Init 函数时,lnit这个函数如何知道应该对d1进行初始化,而不是给d2进行初始化

  • C++中通过引入this指针解决该问题,即:

    • C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数

    • 让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。

    • 只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

5. 1 显示this指针

#include<iostream>
using namespace std;
class Date
{
public:
	void Init(Date* const this, int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

	void Print(Date* const this)
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}
private:
	int _year;     // 年   -> 声明
	int _month;    // 月
	int _day;      // 日
};
	
int main()
{
	Date d1;
	d1.Init(&d1,2022, 7, 17);
	
	Date d2;
	d2.Init(&d2,2022, 7, 18);
	
	d1.Print();
	d2.Print();
	
	return 0;
}
  • 这里我将编译器隐藏起来的this指针显示出来了,但这样编译器是会报错的,
  • 注意:实参和形参位置不能显示传递和接收this指针,但是可以在成员函数内部使用this指针

5.2 this指针的特性  

  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
  2. 只能在“成员函数”的内部使用
  3. this指针本质上是“成员函数”的形参当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
  4. this指针是“成员函数”第一个隐含的指针形参,vs下面传递是通过ecx寄存器传递的,这样this访问变量的时候可以提高效率,不过这些优化取决于编译器

5.3 关于this指针的存储位置

  • 栈区,因为this指针本质上是“成员函数”的形参

5.4 用this指针证明类对象的存储方式

#include<iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
		//cout << this << endl;
	}
private:
	int _a;
};
	
int main()
{
	A* p = nullptr;
	p->Print();  //正常运行
	
	return 0;
}

  •  这段代码不会发生崩溃,编译报错的情况,它是会正常运行的
  • p->Print如果对p进行了解引用,就会报错,但这里是正常运行的,可见类对象的存储方式是猜测三:只保存成员变量,成员函数存放在公共的代码段

6. 类的6个默认成员函数

#include<iostream>
using namespace std;
class A
{

};

int main()
{
	cout << sizeof(A);
	
	return 0;
}

  • 如果一个类中什么成员都没有,简称为空类
  • 空类并不是真的什么都没有,任何类在什么都不写时,
    编译器会自动生成以下6个默认成员函数。

默认成员函数:用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。

6.1构造函数

6.1.1构造函数的特点

  1. 函数名与类名相同。
  2. 无返回值的概念
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载
  5. 不传参也可以调用

6.1.1 无参构造和带参构造 

#include<iostream>
using namespace std;

typedef int DataType;
class Date
{
public:
	Date()
	{
		_year = 2022;
		_month = 7;
		_day = 27;
	}

	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

int main()
{
	Date A;

	A.Print();
	return 0;
}

6.1.2 构造函数的冲突

#include<iostream>
using namespace std;
typedef int DataType;
class Date
{
public:
	Date()
	{
		_year = 1900;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	return 0;
}

  •  这里编译不会通过的,无参的构造函数半缺省/全缺省的构造函数,默认构造函数都叫构造函数,它们只能出现其中的一个

6.1.3 错误使用 

#include<iostream>
using namespace std;

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

int main()
{
	Date d1;
	return 0;
}

  •  这种就是错误的使用构造函数,没有合适的默认构造函数可用

6.1.4 构造函数内语句

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 0, int month = 0, int day = 0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date A;
	return 0;
}
  • 构造函数体中的语句准确的来说应该叫:赋初值,而不能称作初始化
    因为初始化只能初始化一次,而构造函数体内可以多次赋值

6.1.5 构造函数体内调用成员函数的构造函数

#include <iostream>
using namespace std;

class Time
{
public:
	//Time(int hour = 0)
	Time(int hour)
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:
	// 要初始化_t 对象,必须通过初始化列表
	Date(int year, int hour)
	{
		// 函数体内初始化
		_year = year;
		Time t(hour);
		_t = t;//拷贝构造
	}
private:
	int _year;
	Time _t;
};

int main()
{
	Date d(2022, 1);

	return 0;
}

  •  这里想初始化Date里面的_t成员,直接用构造函数在函数体内赋值,是十分不好用的,

6.1.6 初始化列表

#include <iostream>
using namespace std;

class Time
{
public:
	Time(int hour)
	{
		_hour = hour;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int year, int hour, int& x)
		:_year(year)
        ,_t(hour)
		, _N(10)
		, _ref(x)
	{
		// 函数体内初始化
		_year = year;
		_ref++;
	}
private:
	int _year;
	Time _t;
	const int _N;
	int& _ref;
};

int main()
{
	int y = 0;
	Date d(2022, 1, y);

	return 0;
}
  • 类中包含以下成员,必须放在初始化列表位置进行初始

    • 引用成员变量

    • const成员变量(const必须在定义的地方初始化,只有一次机会) 

  • 对于构造函数大部分情况推荐:用初始化列表初始化

6.1.6 初始化列表的初始顺序

#include <iostream>
using namespace std;

class A
{
public:
	// 初始化应该按声明顺序初始化
	A(int a)
		// 成员变量定义
		:_a1(a)
		, _a2(_a1)
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	// 成员变量声明
	int _a2;
	int _a1;
};

int main() {
	A aa1(1);
	aa1.Print();

	return 0;
}

  • 成员变量在类中声明次序就是其在初始化列表中的初始化顺序
    与其在初始化列表中的先后次序无关
  • _a2先声明,就先定义,这时_a是随机值
  • _a1后声明,后定义,这时_a1 = 1 

6.1.7 编译器自动生成的默认成员函数 

#include<iostream>
using namespace std;
typedef int DataType;
class Time
{
public:
	/*Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}*/
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}
  • 在C++中规定默认生成构造函数
    • a:内置类型成员不做处理
    • b:自定义类型成员会去调用他的默认构造函数
  • C++中的这个设计就会导致一个问题,
    • 比如说在这段代码中,生成d1的时候就会调用它的默认构造函数,而_year和_month和_day都是内置类型,只有_t是自定义类型,
    • 对于_t这个对象又会去调用它的默认构造函数,_hour和 _minute和 _second都是内置类型,但是又会因为内置类型成员不做处理。导致最后调用了默认构造函数之后还是随机值

6.1.8 C++ 对内置类型的缺陷的补丁 

 

  • 给的默认值不是初始化,是给的缺省值

6.2 析构函数(清理空间)

6.2.1 析构函数特点 

  1. 析构函数名是在类名前加上字符 ~
  2. 无参数无返回值类型。
  3. 一个类只能有一个构函数d且不能重载若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。 
#include<iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
	//构造函数
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	//析构函数
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};

void TestStack()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}

int main()
{
	TestStack();
	return 0;
}

6.2.2 编译器自动生成的析构函数

#include<iostream>
using namespace std;

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d;
	return 0;
}
  • 编译器生成的默认析构函数对内置类型不做处理,自定义类型会去调用它的析构函数
  • 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数就可以,比如Date类;
  • 有资源申请时,一定要写,否则会造成资源泄漏,比如 Stack 类。 malloc 开辟的空间

6.2.3 析构顺序

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 0)
	{
		_a = a;
		cout << "A(int a = 0)->" << _a << endl;
	}

	~A()
	{
		cout << "~A()->" << _a << endl;
	}
private:
	int _a;
};

A aa3(3);

void f()
{
	static A aa0(0);
	A aa1(1);
	A aa2(2);
	static A aa4(4);
}

// 构造顺序:3 0 1 2 4
// 析构顺序:~2 ~1 ~4 ~0 ~3
int main()
{
	f();
	//f();
	return 0;
}

  • 构造函数: 全局优先,依次构造
  • 析构函数: 栈区优先,先进后出

如果对f()函数调用两次呢?

  • 变量aa1和aa2是局部变量(存放在栈区中),出了函数的作用域就不存在了,生命周期也就没了
  • 变量aa3和aa4被static修饰(存放在静态区),出了函数的的作用域依旧存在,全局变量aa3就不用多说了

6.3 拷贝构造函数 

6.3.1  拷贝构造函数的特点

  • 拷贝构造函数是构造函数的一个重载形式
  • 拷贝构造函数的参数只有一个必须是类类型对象的引用
  • 使用传值方式编译器直接报错,因为会引发无穷递归调用。
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d) 
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

  •  如果使用的是传值传参会发生无穷递归,形参是实参的临时拷贝

6.3.2 深浅拷贝问题

#include<iostream>
using namespace std;
// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
	//构造函数
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	//析构函数
	~Stack()
	{
		if (_array) {
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
}

  •  这里会直接崩溃,这里虽然能把s1拷贝给s2,但是这是值拷贝,是属于浅拷贝
  • 这段空间会被析构两次,导致程序直接崩溃,
  • 需要自己实现深拷贝解决这个问题

6.4 赋值运算符重载

6.4.1 方案一: 运算符重载到类外面

#include<iostream>
using namespace std;
// 全局的operator==
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//private:
	int _year;
	int _month;
	int _day;
};

bool operator==(const Date& d1, const Date& d2) {
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

void Test()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 27);
	//cout << operator==(d1, d2) << endl;
	cout << (d1 == d2) << endl;
}

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

6.4.2 方案二:运算符重载到类里面

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// bool operator==(Date* this, const Date& d2)
	// 这里需要注意的是,左操作数是this,指向调用函数的对象
	bool operator==(const Date& d2)
	{
		return this->_year == d2._year
		&& this->_month == d2._month
			&& this->_day == d2._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

void Test()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 27);
	//cout << d1.operator==(d2) << endl;
	cout << (d1 == d2) << endl;
}

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

  • 将operator定义在类里面,就不用把类中的成员变量公开了,一定程度上解决了方案一的问题

6.4.3 相关注意实现

  • 不能通过连接其他符号来创建新的操作符:比如operator@
  • 重载操作符必须有一个类类型参数
  • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  • .*    ::    sizeof    ? :    . 这5个运算符不能重载。这个经常在笔试选择题中出现。

6.4.4 赋值运算符重载格式

#include<iostream>
using namespace std;

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}

	Time& operator=(const Time& t)
	{
		cout << "Time& operator=(const Time& t)" << endl;
		//防止自己跟自己赋值
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}

		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

int main()
{
	Time a1;
	Time a2 = a1;
	Time a3 = a2 = a1;
	return 0;
}
  • 赋值运算符重载需要支持连续赋值,所以返回*this,且*this出了函数operator的作用域还存在,所以这里是可以用传引用返回的
  • 同样赋值运算符重载也会存在深拷贝问题

7. 操作符前置++和后置++的重载

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	Date& operator++()// 前置加加
	{
		_day += 1;
		return *this;
	}
	Date operator++(int)// 后置加加
	{
		Date temp(*this);
		_day += 1;
		return temp;
	}

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

int main()
{
	Date d;
	Date d1(2022, 1, 13);
	d = d1++;
	d = ++d1;
	return 0;
}

  •  C++中为了解决前置++和后置++中函数名是相同的问题,对后置++默认多传一个参数(编译器做的),用int接收
  • 有了上面的解决方法,前置++和后置++就构造了函数重载  

8. const成员函数

#include<iostream>
using namespace std;

class A
{
public:
	//void Print(const A* const this)
	void Print() const
	{
		//_year = 1;
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	//void Print(A* const this)
	void Print()
	{
		_year = 1024;
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	int _year = 1;   // 年
	int _month = 1; // 月
	int _day = 1;   // 日
};

int main()
{
	A d1;
	const A d2;
	d1.Print();
	d2.Print();

	return 0;
}

  • 被const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数 隐含的this指针 ,表明在该成员函数中 不能对类的任何成员进行修改
  • 被const修饰会改变权限,权限的访问是不能放大的

 9. 取地址及const取地址操作符重载

#include<iostream>
using namespace std;

class Date
{
public:
	// 全缺省的构造函数
	Date(int year = 1,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date* operator&()
	{
		return this;
	}

	const Date* operator&()const
	{
		return this;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

int main()
{
	Date A;
	const Date B;
	cout << &A << endl;
	cout << &B << endl;
	return 0;
}
  • 这两个默认成员函数一般不用重新定义 ,编译器默认会生成
  • 这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如 想让别人获取到指定的内容!

10. explicit关键字

#include <iostream>
using namespace std;

class Date
{
public:
	explicit Date(int year)
	//Date(int year)
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}

	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
	}

	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
};

int main()
{
	Date d1(2022);  // 直接调用构造
	Date d2 = 2022; // 隐式类型转换:构造 + 拷贝构造 + 编译器优化 ->直接调用构造 
	const Date& d3 = 2022;

	return 0;
}

 

  • explicit可以禁掉构造函数的类型转换

 11. 匿名对象

#include <iostream>
using namespace std;
//explicit关键字 + 匿名对象
class Date
{
public:
	explicit Date(int year)
	//Date(int year)
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}

	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
	}

	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
};

class Solution {
public:
	int Sum_Solution(int n) {
		// ...
		return 0;
	}
};

int main()
{

	// 匿名对象 -- 声明周期只有这一行
	Date(2000);

	// 匿名对象 一些使用场景
	Solution slt;
	slt.Sum_Solution(10);

	Solution().Sum_Solution(10);//匿名对象的应用

	return 0;
}

  • explicit匿名对象没用,匿名对象 -- 声明周期只有这一行

12. static成员

静态成员变量一定要在类外进行初始化

12.1 特性一

#include <iostream>
using namespace std;
class Test
{
private:
	static int _n;
};
int main()
{
	cout << sizeof(Test) << endl;
	return 0;
}

  • 静态成员为所有类对象共享,不属于某个具体的对象
  • 静态成员并不计入其总大小之和 

 12.2 特性二

class Test
{
private:
	static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;
  •  静态成员变量必须在类外定义定义时不添加static关键字
  • 注意这里静态成员变量_n虽然是私有,但是我们在类外突破类域直接对其进行了访问。这是一个特例不受访问限定符的限制,否则就没办法对静态成员变量进行定义和初始化了 

  12.3 特性三

class Test
{
public:
	static void Fun()
	{
		cout << _a << endl; //error不能访问非静态成员
		cout << _n << endl; //correct
	}
private:
	int _a; //非静态成员
	static int _n; //静态成员
};
  •  静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  •  含有静态成员变量的类,一般含有一个静态成员函数,用于访问静态成员变量

  12.4 访问静态成员变量的方法

 12.4.1 当静态成员变量为公有时,有以下几种访问方式:

#include <iostream>
using namespace std;
class Test
{
public:
	static int _n; //公有
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
	Test test;
	cout << test._n << endl; //1.通过类对象突破类域进行访问
	cout << Test()._n << endl; //3.通过匿名对象突破类域进行访问
	cout << Test::_n << endl; //2.通过类名突破类域进行访问
	return 0;
}
  •  类名::静态成员 或者 对象.静态成员 来访问

 12.4.1当静态成员变量为私有时,有以下几种访问方式: 

#include <iostream>
using namespace std;
class Test
{
public:
	static int GetN()
	{
		return _n;
	}
private:
	static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
	Test test;
	cout << test.GetN() << endl; //1.通过对象调用成员函数进行访问
	cout << Test().GetN() << endl; //2.通过匿名对象调用成员函数进行访问
	cout << Test::GetN() << endl; //3.通过类名调用静态成员函数进行访问
	return 0;
}
  •  调用静态成员函数访问静态成员变量

12.5 经典问题

12.5.1 静态成员函数可以调用非静态成员函数吗

  •  不可以。因为非静态成员函数的第一个形参默认为this指针,而静态成员函数中没有this指针,故静态成员函数不可调用非静态成员函数。

12.5.2 非静态成员函数可以调用静态成员函数吗 

  • 可以。因为静态成员函数和非静态成员函数都在类中,在类中不受访问限定符的限制

12.5.3 案例:实现一个类,计算程序中创建出了多少个类对象

#include <iostream>
using namespace std;
class A
{
public:
	// 构造函数
	A()
	{
		++_scount;
	}
	// 拷贝构造函数
	A(const A& t) { ++_scount; }

	// 静态成员函数 --  没有this指针
	static int GetCount()
	{
		//_a = 1;

		return _scount;
	}

private:
	int _a;
	// 静态成员变量,属于整个类,生命周期整个程序运行期间,存在静态区
	static int _scount;  // 声明
};

// 类外面定义初始化
int A::_scount = 0;

int main()
{
	A a1;
	A a2;
	A a3(a2);

	cout <<A::GetCount() << endl;

	return 0;
}
  •  静态成员所有类对象所共享,不属于某个具体的对象,存放在静态区
  •  静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  • 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  •  静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制

 13. 友元

  1. 友元函数可以访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用原理相同
  6. 友元关系是单向的,不具有交换,不能传递
#include <iostream>
using namespace std;
class Date
{
	// 友元: 使私有在类外面能够访问
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month; int _day;
};

ostream& operator<<(ostream& _cout, const Date& d) {
	_cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

istream& operator>>(istream& _cin, Date& d) {
	_cin >> d._year;
	_cin >> d._month;
	_cin >> d._day;
	return _cin;
}
int main()
{
	Date d;
	cin >> d;
	cout << d << endl;
	return 0;
}
  • this指针默认是第一个参数也就是左操作数

  • 如果将operator<<重载成成员函数,那么使用的时候就会很尴尬,比如d >> cin,所以出现了友元来解决这个问题

  • 注意: 友元没有传递性,如果B是A的友元,C是B的友元,则不能说明C时A的友元。

13.1 友元的缺陷

  • 友元提供了一种突破封装的方式,虽然提供了便利,但是友元会增加耦合度,破坏了封装,所以友元不宜多用,

14. 内部类

#include <iostream>
using namespace std;

class A
{
public:
	// B定义在A的里面
	// 1、受A的类域限制,访问限定符
	// 2、B天生是A的友元,即B可以访问A中的成员变量
	class B
	{
	public:
		void foo(const A& a)
		{
			cout << k << endl;//OK
			cout << a._h << endl;//OK -- 友元
		}
	private:
		int _b;
	};

private:
	int _h;
	static int k;
};

int A::k = 1;

int main()
{
	cout << sizeof(A) << endl; 
	A a;
	A::B b;

	return 0;
}
  •  里面的类是外面类的友元即里面的类可以直接访问外面的成员变量

15. 编译器的优化

15.1 对连续表达式的优化

#include <iostream>
using namespace std;
class W
{
public:
	W(int x = 0)
	{
		cout << "W()" << endl;
	}

	W(const W& w)
	{
		cout << "W(const W& w)" << endl;
	}

	W& operator=(const W& w)
	{
		cout << "W& operator=(const W& w)" << endl;
		return *this;
	}

	~W()
	{
		cout << "~W()" << endl;
	}
};

void f1(W w)
{

}

void f2(const W& w)
{

}

int main()
{
	W w1;
	f1(w1);
	f2(w1);
	cout << endl << endl;

	f1(W()); // 构造+拷贝构造--编译器的优化--直接构造
	
	return 0;
}

 

  • 连续一个表达式步骤中,连续构造一般都会优化
  • 构造+拷贝构造--编译器的优化--直接构造

15.2 对返回值的优化

#include <iostream>
using namespace std;

class W
{
public:
	W(int x = 0)
	{
		cout << "W()" << endl;
	}

	W(const W& w)
	{
		cout << "W(const W& w)" << endl;
	}

	W& operator=(const W& w)
	{
		cout << "W& operator=(const W& w)" << endl;
		return *this;
	}

	~W()
	{
		cout << "~W()" << endl;
	}
};

W f(W u)
{
	W v(u);
	W w = v;
	return w;
}

int main()
{
	W x;
	W y = f(x); // 1次构造  4次拷贝

	return 0;
}

 

  •  传值返回是需要借助寄存器的,编译器会这里做优化,不借助中间的寄存器,拷贝构造就会少一次,
  • linux中的g++优化更大,比vs2019都少一次

16. 类和对象经典题目

16.1 题目一:求1+2+3+…+n

题目描述: 

求1+2+3+…+n,要求不能使用乘除法forwhileifelseswitchcase等关键字及条件判断语句(A?B:C)

直接new一堆类的数组 

#include <iostream>
#include <string>
using namespace std;
class Add {
public:
	Add() {
		_num++;
		_ret += _num;
	}
	static int _num;
	static int _ret;
};

int Add::_num = 0; 
int Add::_ret = 0;

class Solution {
public:
	int Sum_Solution(int n) {
		// 解决多个案例
		Add::_num = 0;
		Add::_ret = 0;
		Add* p = new Add[n];
		return Add::_ret;
	}
};

int main()
{
	cout << Solution().Sum_Solution(10) << endl;
	return 0;
}
  • 注意:多个测试案列的时候,记得清零(static)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值