2024年类和对象(下)_鹿九丸(1),字节跳动C C++架构师学习笔记

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

private:
A _aobj; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};


问:对于没有默认构造函数的自定义类型的成员,假如不采用初始化列表的方式,我们还有什么办法来对其进行初始化?


答:



B(int a)
{
A aa(a);//A是自定义类型的类型名
_aa = aa;//_aa是自定义类型的成员变量名
}


注意:初始化列表中的参数可以有四种来源:


* **形参。**

 

A(int a)
:_a(a)//_a是成员变量
{

}

* **全局变量。**

 

A()
:_ref(iref)//ref是成员变量
{

}

* **常量值。**

 

A()
:_b(10)//_b是成员变量
{

}

* **C99中声明时的初始化。**

 

class A
{
public:
A(int a)
{

}

private:
int _a = 10;
};
//此时我们并未显式写出初始化列表,但上面的代码在编译器看来应该是像下面一样的
class A
{
public:
A(int a)
:_a(10)
{

}

private:
int _a = 10;
};



注意点1:


下面这种写法是错误的:



class A
{
public:
A(int a)
:_a(a = 10)
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A a1;
return 0;
}


这种写法编译器会报错,编译器会提示没有合适的默认构造函数可用,于是我们进行下面的修改:



class A
{
public:
A(int a = 20)
:_a(a = 10)
{
_a = a;
cout << a << endl;
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A a1;
return 0;
}


输出结果如下:


![image-20220522221835869](https://img-blog.csdnimg.cn/img_convert/d0930856a5655c3c9991514f6489ace6.png)


分析:初始化列表中的赋值表达式`a = 10`成功执行了,所以a被赋值给了10,同时返回值a即10将\_a初始化为10,然后再执行`_a = a`,这个语句执行了又相当于没有执行,并没有对最终结果产生影响。


再看下面的代码:



class A
{
public:
A(int a = 20)
:_a(a = 10)
{
_a = a;
cout << a << endl;
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A a1(50);
return 0;
}


输出结果:


![image-20220522222212980](https://img-blog.csdnimg.cn/img_convert/39c3639674623b595f552670df9f4f3d.png)


分析:50被赋值给形参a,所以并没有对最终的结果产生任何的影响。


此时再看下面的代码:



class A
{
public:
A(int a = 20)
:_a(a = 10)
{
_a = a;
cout << a << endl;
cout << _a << endl;
}
private:
int _a = 50;
};
int main()
{
A a1;
return 0;
}


输出结果:


![image-20220522222420814](https://img-blog.csdnimg.cn/img_convert/e939337da0715d2de70b311d8d9c9cbf.png)


输出结果仍然为10,此时我们可以得出一个结论:声明中初始化给的缺省值的优先级(\_a = 50)比我们在初始化列表中显式给的a的优先级要低。声明中给的初始化的值是备用选择。类似于我们通常定义的函数中的缺省值一样。


结论:C++11在类的定义时对于成员变量给的缺省值是作用在初始化列表中的。


结论:初始化列表无论什么情况下都会走一遍,无论我们是否显式的给出。无论我们给的构造函数是否会形成默认构造函数,编译器都会在初始化列表中对自定义类型调用它的默认构造函数进行初始化。例如下面的代码:



class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
cout << _hour << “-” << _minute << endl;
}
private:
int _hour;
int _minute;
};
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
cout << _year << “-” << _month << “-” << _day;
}
private:
int _year;
int _month;
int _day;
Time t1;
};
int main()
{
Date d1(2022, 5, 23);
return 0;
}


程序运行结果:


![image-20220523102531799](https://img-blog.csdnimg.cn/img_convert/ac2be923258cdae3074646ebb74269c6.png)


分析:我们上面的Date构造函数并不是三种默认构造函数的一种,但还是对自定义类型调用了自定义类型成员变量t1即Time类的默认构造函数。


3. **尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。**

 注意:之前我们提到,有三种默认构造函数:全缺省构造函数、无参构造函数、我们不写时编译器默认生成的构造函数,无论是上面的哪一种,都会对自定义类型默认调用它们的构造函数,这个过程就是在初始化列表完成的,至于内置类型,因为没有值去初始化,所以呈现出毫无意义的数值。

 注意下面一种情况:

 

class Date
{
public:
Date(const Date& q)//拷贝构造函数
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}

 上面的代码程序会报错:没有合适的默认构造函数可用。

 注意:拷贝构造函数本身也是构造函数,不过是特殊的构造函数,但是拷贝构造函数一旦出现,编译器也将不再生成默认的构造函数。上面的代码中出现了拷贝构造函数,所以默认构造函数就不再自动生成,所以编译器会报错。
4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

 

class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{}

void Print() {
	cout << _a1 << " " << _a2 << endl;
}

private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
A. 输出1 1
B. 程序崩溃//一般是野指针之类的出现
C. 编译不通过
D. 输出1 随机值

 运行结果:

 ![image-20220523105217228](https://img-blog.csdnimg.cn/img_convert/cc18e72e3d0cf08104836ab02033884f.png)

 所以正确答案应该是D,为什么?因为成员变量在初始化列表中的初始化顺序只和成员变量在类中的声明的顺序有关,在上面的例子中,\_a2先声明的,所以在初始化列表中先执行的是`\_a2(\_a1)`(此时\_a1是随机值,因为并没有进行初始化),所以\_a2是随机值,然后再执行的是`\_a1(a)`,然后\_a1被初始化为1,所以输出结果为`1 随机值`。


### 1.3 explicit关键字


构造函数不仅可以构造与初始化对象,**对于单个参数的构造函数,还具有类型转换的作用**。



class Date
{
public:
Date(int year)
:_year(year)
{}
private:
int _year;
};
void TestDate()
{
Date d1(2022);

// 用一个整形变量给日期类型对象赋值
// 实际编译器背后会用2022构造一个无名对象,最后用无名对象给d2对象进行赋值
Date d2 = 2022;

}


画图来理解:


![image-20220523113214999](https://img-blog.csdnimg.cn/img_convert/c68a6a4790f7e1098e6be4dd392eead3.png)


在上面的这个过程中会调用两个函数:构造函数 + 拷贝构造(后面同类型变量的赋值是调用的是拷贝构造函数,因为是创建一个新的对象d2)


当然,在这个地方很多编译器会进行一个优化,会直接使用2022来构造一个Date类型的d2变量,省去了中间类型转换和临时变量的过程,只会调用一次构造函数。


explicit关键字的作用:用explicit修饰构造函数,将会禁止单参构造函数的隐式转换。。即无法将int类型转换成Date类型生成临时变量,继续运行上面的代码程序会报错,因为无法进行类型转换的操作。


看下面的代码:



class Date
{
public:
Date(int year)
{
_year = year;
}
private:
int _year;
};
void TestDate()
{
Date d1(2022);
Date& d2 = 2022;
}
int main()
{
TestDate();
return 0;
}


上面的代码运行后会出现问题,为什么?因为临时变量具有常性,不能被普通的引用所引用,这属于权限的扩大,如果想要引用临时变量,只能用常引用来引用,像下面代码所演示:



class Date
{
public:
Date(int year)
{
_year = year;
}
private:
int _year;
};
void TestDate()
{
Date d1(2022);
const Date& d2 = 2022;
}
int main()
{
TestDate();
return 0;
}


注意:这个临时变量被引用之后只有出了作用域之后才会被彻底销毁!


那么这种语法有什么用呢?看下面的代码:



void Func(const std::string& s)
{

}
int main()
{
std::string s1 = “hello”;
Func(s1);//这种传参可以是毋庸置疑的
Func(“hello”);//有了临时变量进行隐式类型转换后这种传参也变得合法,且使传参变得更加方便
//"hello"发生了隐式类型转换后形成了一个临时变量,然后这个临时变量具有常性,类型为const string,所以必须用常引用来接收
}


## 2. static成员


### 2.1 概念


声明为**static的类成员**称为**类的静态成员**,用**static**修饰的**成员变量**,称之为**静态成员变量**;用**static修饰**的**成员函数**,称之为**静态成员函数**。**静态的成员变量一定要在类外进行初始化**


**面试题:实现一个类,计算程序中创建出了多少个类对象**。


首先先看下面一个面试题,在下面的程序代码中,类型A的拷贝和构造函数被调用了多少次?



class A{};
A Func(A a)
{
A copy(a);
return copy;
}
int main()
{
A a1;
A a2 = Func(a1);
}


我们通过修改下面的代码就能够计算出调用次数:



int count = 0;
class A
{
public:
A()
{
count++;
}
A(const A& aa)
{
count++;
}
};


但是全局变量并不好,因为全局变量一般定义在.h文件里,常常会包含在其它的.cpp文件中,所以往往在项目中容易出现问题,我们推荐用静态成员变量:



class A
{
public:
void Print()
{
cout << _count << endl;//类内直接访问即可
}
A()
{
count++;
}
A(const A& aa)
{
count++;
}
private:
static int _count;//声明
};
int A::_count = 0;//定义,静态成员变量只能在类外定义
//假如_count是public权限的,在类外有两种访问方式:
Date d1;
d1._count;//通过具体某个类的实例化对象来进行访问
A::_count;//通过类来进行访问



> 
> 问:静态成员变量和普通成员变量有什么区别?
> 
> 
> 答:静态成员变量不占用栈区上的空间,存在于静态区,属于整个类,属于类的所有对象,而不属于某个对象,当我们用`sizeof()`求某个类的成员或者类类型的大小的时候,不会包括静态成员变量。例如:
> 
> 
> 
> ```
> class A
> {
> public:
> 	A()
> 	{
> 		count++;
> 	}
> 	A(const A& aa)
> 	{
> 		count++;
> 	}
> 
> private:
> 	static int count;
> };
> 
> int main()
> {
> 	cout << sizeof(A) << endl;
> }
> 
> ```
> 
> 输出结果为1。
> 
> 
> 问:那么静态成员变量是否在初始化列表中进行初始化的呢?
> 
> 
> 答:不是,因为只有具体的对象在初始化的时候才会在初始化列表进行初始化。
> 
> 
> 


除了成员变量可以是静态的,成员函数也可以是静态的,成员函数最为重要的特征是:没有this指针。


我们在类外访问静态成员函数有两种方式:



class A
{
public:
static void Func()
{

}

};
int main()
{
A a1;
a1.Func();//使用类的实例化成员来进行调用静态成员函数
A::Func();//使用类的类名来调用静态成员函数
return 0;
}


上面两种调用方式无论是哪一种,都只是为了突破类域。


### 2.2 特性


1. 静态成员为所有类对象所共享,不属于某个具体的实例
2. 静态成员变量必须在类外定义,定义时不添加static关键字
3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值
6. 未初始化的静态成员变量会默认为0,但是一定要显式定义,可以不初始化。


【问题】


1. 静态成员函数可以调用非静态成员函数吗?
2. 非静态成员函数可以调用类的静态成员函数吗?


## 3.C++11 的成员初始化新玩法。


C++11支持非静态成员变量在声明时进行初始化赋值,**但是要注意这里不是初始化,这里是给声明的成员变量缺省值。**


注意:静态成员变量无法赋缺省值,只能在类外进行定义并初始化,为什么?因为此处只是声明,并不是真正的定义,初始化列表只有在显式创建对象的时候才会用到。


下面的代码就是错误的:



class A
{
private:
int _a = 1;
static int _ref = 10;//此处就是错误的,因为静态成员变量只能在类外进行定义并初始化
};
//下面才是正确的
class A
{
private:
int _a = 1;
static int _ref = 1;//此处就是错误的,因为静态成员变量只能在类外进行定义并初始化
};
int A::_ref = 10;


## 4. 友元



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


### 4.1 友元函数


友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。


友元函数的经典使用:



class Date
{
friend ostream& operator<<(ostream& out, const Date& d);//在operator<<函数内可以在类外访问Date类的成员变量
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out,const Date& d)
{
out << d._year << “-” << d._month << “-” << d._day << endl;
return out;
}

int main()
{
Date d1(2022, 5, 20);
cout << d1;
return 0;
}


说明:


* 友元函数可访问类的私有和保护成员,但不是类的成员函数
* 友元函数不能用const修饰
* 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
* 一个函数可以是多个类的友元函数

 下面是一个例子:

 

class Date;
//问:此处为什么要加一个前置声明?
//答:因为在Time里面对Print友元函数的声明中,向前找是找不到Date类的声明或者定义的,所以必须在此处声明Date是一个类的名字,关于具体的Date类的定义可以去后面找
class Time
{
friend void Print(const Date& d, const Time& t);//Print是Time类的友元函数,在Print函数内可以访问Time类的成员变量
public:
Time(int hour = 0, int minute = 0, int second = 0)
{
_hour = hour;
_minute = minute;
_second = second;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
friend void Print(const Date& d, const Time& t);//Print是Date类的友元函数,在Print函数内可以访问Date类的成员变量
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
void Print(const Date& d,const Time& t)
{
cout << d._year << “-” << d._month << “-” << d._day << “-”;
cout << t._hour << “-” << t._minute << “-” << t._second << endl;
}
int main()
{
Date d1(2022, 5, 20);
Time t1;
Print(d1, t1);
return 0;
}

* 友元函数的调用与普通函数的调用和原理相同


注意:下面的这种友元函数的使用方法是错误的,且目前C++没有这种语法来实现:



class Time
{
friend void Date::Print();
public:
Time(int hour = 0, int minute = 0, int second = 0)
{
_hour = hour;
_minute = minute;
_second = second;
}
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << “-” << _month << “-” << _day << “-”;
cout << _t._hour << “-” << _t._minute << “-” << _t._second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d1(2022, 5, 20);
d1.Print();
return 0;
}


如果我们想在Date类中访问Time类中的成员变量,只有一种方法,就是将Date声明为Time的友元类,出吃之外没有其它的方法,即C++不支持将某个类中的成员函数声明为某个类的友元函数。


### 4.2 友元类


友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。



![img](https://img-blog.csdnimg.cn/img_convert/da5663939119dade42ad176f86fb3a87.png)
![img](https://img-blog.csdnimg.cn/img_convert/484e307c7bf4add003ef1284c626ae95.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**

**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**

**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**

s Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << "-";
		cout << _t._hour << "-" << _t._minute << "-" << _t._second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d1(2022, 5, 20);
	d1.Print();
	return 0;
}

如果我们想在Date类中访问Time类中的成员变量,只有一种方法,就是将Date声明为Time的友元类,出吃之外没有其它的方法,即C++不支持将某个类中的成员函数声明为某个类的友元函数。

4.2 友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

[外链图片转存中…(img-xsVoe4Lg-1715676604253)]
[外链图片转存中…(img-0gM5Okkb-1715676604253)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值