C++类和对象

ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。
类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。

类的访问限定符及封装

1. public修饰的成员在类外可以直接被访问
2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
4. 如果后面没有访问限定符,作用域就到 } 即类结束。
5. class的默认访问权限为private,struct为public(因为struct要兼容C)

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

C++中struct和class的区别是什么?

C++需要兼容C语言,所以C++中struct可以当成结构体使用。另外C++中struct还可以用来
定义类。和class定义类是一样的,区别是struct定义的类默认访问权限是public,class定义的类
默认访问权限是private。注意:在继承和模板参数列表位置,struct和class也有区别。

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 ::
作用域操作符指明成员属于哪个类域。

class Date {
public://公共区域,外部可以访问
	void Init(int year, int month, int day) {
		_year=year;
		 _month=month;
		 _day=day;
	}

private://私有区域,外部不能访问
	int _year;//声明,不能访问,没有开空间
	int _month;
	int _day;
};
int main() {
	Date a;
	a.Init(1, 2, 3);
}

类的实例化

.1.类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没
有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个
类,来描述具体学生信息。

2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量

类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算
一个类的大小?

包含了各种成员,类成员变量和类成员函数,类的大小可以直接按变量大小去计算,类函数可以想象成设计图,是一种公共的代码区,不需要计算函数大小

类存储方式,那计算机到底是按照那种方式来存储的?

如果是空类或者只有成员函数,没有成员变量,则占位按1算,表示对象存在过,有成员变量按成员变量长度算,如果存在char这种占1个位的类型,则和c语言一样遵循内存对齐规则(64位情况下,假如一个int一个char,那么就要补到8个字节)

【面试题】
1. 结构体怎么对齐? 为什么要进行内存对齐?

64位情况下,假如一个int一个char,那么就要补到8个字节
2. 如何让结构体按照指定的对齐参数进行对齐?能否按照3、4、5即任意字节对齐?

假设32位的情况下,结构体里面先char类型的对象后int类型的对象,如果不对齐,那么int类型需要访问2次才能访问完,使用#pargam pack去控制对齐
3. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景

this指针

 this指针的引出

指向当前调用的对象

void Init(Date* this, int year, int month, int day)
//c++规定不能显示写出this在实参和形参
{
this->_year = year;//但是在类里面可以显示的写
this->_month = month;
this->_day = day;
}
int main(){
Date.d1;
d1.Init(&d1,2023,4,2);
    return 0;
}

同一个变量中,里面调用地址一样,不同的变量中地址不一样,因为创建完栈帧使用后就销毁了

class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
this->Print();//同一个变量里面地址一样
}
void Print()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
private:
int _year;   // 年
int _month;   // 月
int _day;    // 日
};
int main()
{
Date d1, d2;
d1.Init(2022,1,11);
d2.Init(2022, 1, 12);
d1.Print();
d2.Print();
return 0;
}

【面试题】
1. this指针存在哪里?

this是一个形参,一般是存在栈帧里面,vs下面一般会用ecx寄存器直接传递
2. this指针可以为空吗?

可以为空

3.成员函数的地址放在哪?空指针可以调用吗

成员函数的地址存放在公共代码区域,空指针可以直接调用他的成员函数,c++变成汇编语言的时候直接去call成员函数的地址,绕过成员变量

构造函数

 特性
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务并不是开空间创建对象,而是初始化对象。
其特征如下:
1. 函数名与类名相同。
2. 无返回值。(不需要写void)
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载

date() {//传参的时候不能带参数和括号,否则会报错
	 year = 1;
	 month = 1;
	 day = 1;
}
date(int year,int month,int day) {//和传参要带括号,和第一种情况构成函数重载
	 _year =year ;
	 _month = month;
	 _day = day;
}
date(int year=1,int month=1,int day=1) {//上下合并,提供全全省
	 _year =year ;
	 _month = month;
	 _day = day;
}

构造函数,也是默认成员函数,我们不写,编译器会自动生成

 Date(int year=1, int month=1, int day=1)//不写会生成默认函数得出值-858993460--858993460--858993460
	{
		_year = year;
		_month = month;
		_day = day;
	}

编译生成默认构造函数的特点:

1.我们不写才会生成,我们写了任意一个构造函数就不会生成

2.内置类型(int double等)的成员不会处理(c++11,声明给缺省值)

public:
 Date()
	{
		
		_month =3;
		_day = 1;
	}
private:
	int _year=1;//声明给缺省值
	int _month=1;
	int _day=1;

3.自定义类型(类)的成员才会处理,回去调用这个成员的构造函数注意:析构
函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。

6. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会
生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默
认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的
默认构造函数并没有什么用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类
型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看
下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员
函数

7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个(多个并存就会存在二义性)。
注意:无参构造函数全缺省构造函数我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。(不传参就可以调用)

 初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。

1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(且该类没有默认构造函数时)

3.内置类型也推荐使用初始化列表,当然内置类型在函数体内也没有明显问题,能使用初始化列表就使用初始化列表,初始化列表按照声明的顺序去初始化

class Time {
public:
	Time(int hour) {
		cout << "time1" << endl;
		_hour = hour;
	}
private:
	int _hour;
};

class Date {
public :
	Date(int year, int hour)
		:_t(hour)//自定义类型成员,推荐使用列表初始化
				 //初始化列表可以认为是成员变量定义的地方
	{
		_year = year;
	}
	
private:
	int _year;
	Time _t;
};

explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值
的构造函数,还具有类型转换的作用

class Date
{
public:
// 1. 单参构造函数,没有使用explicit修饰,具有类型转换作用
// explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译
explicit Date(int year)
:_year(year)
{}
/*
// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,没有使用explicit修饰,具
有类型转换作用
// explicit修饰构造函数,禁止类型转换
explicit Date(int year, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
*/
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1(2022);
// 用一个整形变量给日期类型对象赋值
// 实际编译器背后会用2023构造一个无名对象,最后用无名对象给d1对象进行赋值
d1 = 2023;
// 将1屏蔽掉,2放开时则编译失败,因为explicit修饰构造函数,禁止了单参构造函数类型转
换的作用
}

析构函数

概念
析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由
编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作(类比destroy)

 特性
析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载(内置类型不处理,自定义类型会调用析构函数,自定义类型在无初始化列表的情况下必须提供全缺省值)
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器
生成的默认析构函数

对自定类型成员调用它的析构函数,由于栈的特性,后生成的会先生成析构函数

~Stack(){
		free(a);
		a = nullptr;
		capacity = top = 0;

	}

拷贝构造函数

特征
拷贝构造函数也是特殊的成员函数,其特征如下:
1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个且必须是同类型对象的引用,使用传值方式编译器直接报错,
因为会引发无穷递归调用

3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。(c++内置类型-值拷贝,自定义类型-调用它的拷贝)

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的。

4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
当然像日期类这样的类是没必要的。

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝

5. 拷贝构造函数典型调用场景:
使用已存在对象创建新对象
函数参数类型为类类型对象
函数返回值类型为类类型对象

#include<iostream>
#include<vector>
#include<string>
#include<assert.h>
using namespace std;
class Date
{
public:
	 Date(int year=1, int month=1, int day=1)//不写会生成默认函数得出值-858993460--858993460--858993460
	{
		 cout << "Date" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	 Date(Date& d)//拷贝构造函数,在date这边是值拷贝
	 {
		 cout << "copyDate" << endl;
		 _year = d._year;
		 _month = d._month;
		 _day = d._day;
	 }
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	~Date(){
		cout << "~Date" << endl;
	}
private:
	int _year=1;
	int _month=1;
	int _day=1;
};

class Stack {//不占内存空间,存在文件系统中
public:
	Stack(size_t n = 4) {
		cout << "Stack" << endl;
		if (n == 0) {
			a = nullptr;
			capacity = top = 0;
		}
		else {
			a = (int*)malloc(sizeof(int)*n);
			if (a == nullptr) {
				perror("fail");
				exit(-1);

			}
			top = 0;
			capacity = n;
		}
	}
	Stack(const Stack& s) {//拷贝构造函数,在栈上面是调用拷贝,
		//如果是值拷贝那么存储区域就是指向的同一块空间,释放的时候会释放两次造成运行崩溃
		cout << "copyStack" << endl;
		a = (int*)malloc(sizeof(int) * s.capacity);
		if (a == nullptr) {
			perror("fail");
			exit(-1);
		}
		memcpy(a, s.a, sizeof(int) * s.top);
		capacity = s.capacity;
		top = s.top;
	}
	void Push(int x) {
		if (top == capacity) {
			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
			a = (int*)realloc(a, sizeof(int) * newcapacity);
			capacity = newcapacity;
		}
		a[top++] = x;
	}
	void Pop() {
		assert(top > 0);
		top--;

	}
	int Empty() {
		return top == 0;
	}

	int Top() {
		return a[top - 1];
	}

	void Print() {
		while (!Empty()) {
			cout <<Top() <<" ";
			Pop();
		}
		cout << endl;
	}
	~Stack(){
		cout << "~Stack"<<endl;
		free(a);
		a = nullptr;
		capacity = top = 0;

	}
	
private:

	//成员变量
	int* a;
	int top;
	int capacity;
};
class myqueue {

private:
	Stack _pushst;
	Stack _pop1;
};
void fun1(Date s) {
	s.Print();
}
void fun2(Stack s) {
	s.Print();
}
int main() {
	Date s1;
	fun1(s1);
	s1.Print();
	Date s2(s1);
	Stack st;
	st.Push(1);
	st.Push(2);
	st.Push(3);
	st.Push(4);
	
	fun2(st);
	st.Print();
	myqueue m1;
	myqueue m2 = m1;

	return 0;
}

运算符重载

运算符重载


C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
藏的this
.* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出

不能改变操作符的操作数个数,一个操作符是几个操作数,那么重载的时候就有几个参数(sizeof,::,?:,.,.*不能重载)

赋值运算符重载


1. 赋值运算符重载格式
参数类型:const T&,传递引用可以提高传参效率
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this :要复合连续赋值的含义

2. 赋值运算符只能重载成类的成员函数不能重载成全局函数

3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注
意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
重载完成赋值。

	// 赋值运算符重载

  // d2 = d3 -> d2.operator=(&d2, d3)

	Date& operator=(const Date& d) {//支持连续赋值
		if (this != &d) {
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		
		return *this;
	}

const成员


将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

const A* operator&() const{//const对象返回值也要const
	return this;
}
A* operator&() {//普通对象返回值要普通的
	return this;
}
默认成员函数编译器不写会自动生成

总结:只读函数可以加const,内部不涉及修改生成

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

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

友元

        友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用,相当于可以访问私有类型。
友元分为:友元函数和友元类

友元函数


问题:现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的
输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作
数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成
全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。

	 inline friend ostream&  operator<<(ostream& out, const Date& d) {
				out << d._year << "-" << d._month <<"-"<< d._day << endl;
				return out;
			}
		
		 inline friend istream& operator>>(istream& in, Date& d) {
			 in >> d._year >> d._month >> d._day ;
			 assert(d.checkdate());
			 return in;
		 }

友元类


友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接
访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。

内部类


概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,
它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越
的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访
问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系

 去掉public这种情况是不能访问B中的元素的

class A {
//public:

	class B {//内部类默认是外部类的友元

	private:
		int n;
	};
private:
	int m;
};

int main() {
	A i;
	A::B x;
	return 0;
}

匿名对象

匿名对象不需要写名字,只需要定义即可,他的函数生命周期只在执行的那一行代码

int n = 0;
int m = 0;

class A {
public:
	A() {
		m++;
		n++;
	}
	A(const A& t) {
		n++;
		m++;
	}
	~A() {
		m--;
	}
private:

};


int main() {
	A a1;
	A a2;
	A a3;
	cout << n << " " <<m<< endl;
	A();
	cout << n << " " <<m<< endl;
	return 0;
}


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值