🎬 鸽芷咕 :个人主页
🔥 个人专栏 : 《C++干货基地》《粉丝福利》
⛺️生活的理想,就是为了理想的生活!
引入
哈喽各位铁汁们好啊,我是博主鸽芷咕《C++干货基地》是由我的襄阳家乡零食基地有感而发,不知道各位的城市有没有这种实惠又全面的零食基地呢?C++ 本身作为一门篇底层的一种语言,世面的免费课程大多都没有教明白。所以本篇专栏的内容全是干货让大家从底层了解C++,把更多的知识由抽象到简单通俗易懂。
⛳️ 推荐
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
文章目录
- 引入
- [⛳️ 推荐](#⛳️ 推荐)
- [一、const 成员函数](#一、const 成员函数)
-
- [1.1 什么是const 成员函数](#1.1 什么是const 成员函数)
- [1.2 const成员函数的注意事项](#1.2 const成员函数的注意事项)
- 总结
- 二、取地址及const取地址操作符重载
-
- [2.1 取地址操作的意义](#2.1 取地址操作的意义)
- 三、重新认识构造函数
-
- 3.1构造函数体赋值
- [3.2 初始化列表](#3.2 初始化列表)
- 四、explicit关键字
-
- [4.1 构造函数的隐式类型转换](#4.1 构造函数的隐式类型转换)
- [4.2 隐式转换的作用](#4.2 隐式转换的作用)
- [4.2 explicit关键字的使用](#4.2 explicit关键字的使用)
一、const 成员函数
1.1 什么是const 成员函数
cosnt 的成员函数其实就是在我们 函数的括号外 多加一个 const
void Dlsplay() const
- 其他的作用是修饰 隐含的 this 指针,使其不能修改。
1.2 const成员函数的注意事项
const 成员可以直接修饰this指针那么使用起来有什么要注意的嘛?
- 下面我们看一下这些代码来思考一下
cpp
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
void Print() const
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void Test()
{
Date d1(2022, 1, 13);
d1.Print();
const Date d2(2022, 1, 13);
d2.Print();
}
int main()
{
Test();
return 0;
}
- const对象可以调用非const成员函数吗?
- 不能 const 对象调用非const成员会导致,权限的放大所以会出现错误
- 非const对象可以调用const成员函数吗?
- 可以 非const 成员调用 const 成员函数属于权限的缩小,权限是可以缩小的
- const成员函数内可以调用其它的非const成员函数吗?
- 不可以,这样会导致权限的放大
- 非const成员函数内可以调用其它的const成员函数吗?
- 可以,非const 成员,调用const 成员是权限的缩小
总结
1. 在成员函数里如果我们只对成员变量读访问,那么建议加上 cosnt指针。
2. 在成员函数里如果我们要对成员变量进行修改,不能加上 cosnt指针。(否者修改不了成员变量)
二、取地址及const取地址操作符重载
2.1 取地址操作的意义
取地址操作符顾名思义,就对我们的
&
取地址符号进行重载使其能获取到成员变量的地址
- 但是一般都是默认生成的,除非我们想让取地址符号取的是指定位置的地址
cpp
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
三、重新认识构造函数
3.1构造函数体赋值
以往我们在定义构造函数的时候都是在构造函数内进行赋值的,那么我们创建成员变量是否也是在构造函数里面呢?
- 如果构造函数是创建的话那么,我们声明成员变量的时候给的默认值是定义嘛?
cpp
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year = 2022;
int _month = 06;
int _day = 25;
};
- 构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
- 所以构造函数并不是初始化成员变量的地方,而我们在进行类声明的时候给的的默认值夜也只是声明
3.2 初始化列表
在C++中规定了所有成员变量在初始化的时候都是在初始化列表进行初始化的
- 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟
一个放在括号中的初始值或表达式。
cpp
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
- 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
- 类中包含以下成员,必须放在初始化列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类型成员(且该类没有默认构造函数时)
规则一
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
这个我相信很好了解,初始化列表不管我们写没写都会在初始化列表进行初始化
- 所以对于简单的变量初始化建议使用初始化列表
- 一些复杂的类初始化可以使用在构造函数体内进行初始化
规则二
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
🍸 代码演示:
cpp
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 随机值
这里的答案是选D
虽然我们在初始化列表先写的是
_a1
但是初始化列表是按照声明的顺序来进行初始化的,对_a2(_a1)
进行初始化的时候 __a1
还是一个随机值,_a2
就被赋值成了_a1
的随机值
四、explicit关键字
4.1 构造函数的隐式类型转换
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值
的构造函数,还具有类型转换的作用。
cpp
class A
{
public:
A(int x)
{
this->_x = x;
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _x;
};
int main()
{
A a1(1);
A a2 = 3;
return 0;
}
这里我们既可以使用构造函数来进行赋值,还可以使用隐式类型转换的方法来进行赋值。
- 其主要原理是 使用
=
号赋值时,会先用 1 构造一个临时变量然后再调用拷贝构造构造函数 - 也就是 先构造->在拷贝构造
但是编译器目前太智能了,对同一个表达式连续的构造会合二为一优化为一步
- 除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
cpp
class Data
{
public:
Data(int year, int month = 10, int day = 1)
{
cout << "data()" << endl;
}
~Data()
{
cout << "~data()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data a1(2021);
Data a2 = 2022;
return 0;
}
4.2 隐式转换的作用
隐式转换可以说是非常的好用了以后我们在很多地方都可以用到,以往我们使用链表存储类的话每次push 都需要,push 成员变量,但是有了隐式类型转换就可以直接插入自动转换为我们需要的类了。
- 而且我们在类里面去给其他类进行使用缺省值的时候也是非常方便
cpp
class C
{
public:
//explicit C(int x = 0)
C(int x = 0)
:_x(x)
{}
C(const C& cc)
{
cout << "C(const C& cc)" << endl;
}
private:
int _x;
};
C xx(1);//定义全局变量来赋缺省值
class B
{
private:
// 缺省值
int a = 1;
int* p1 = nullptr;
int* p2 = (int*)malloc(4);
C cc1 = xx; // 虽然可以,但是很费劲
C cc2 = 2;
};
- push 插入时直接使用隐式类型转换,不需要在插入相同类型的类了
cpp
class C
{
public:
//explicit C(int x = 0)
C(int x = 0)
:_x(x)
{}
C(const C& cc)
{
cout << "C(const C& cc)" << endl;
}
private:
int _x;
};
class Stack
{
public:
void Push(const C& c)
{
//
}
};
int main()
{
Stack st;
C cc3(3);
st.Push(cc3);
st.Push(4);
return 0;
}
4.2 explicit关键字的使用
explict
的关键字是用来修饰构造函数的一旦使用了explicit
修饰构造函数,禁止类型转换
cpp
class A
{
public:
explicit A(int x)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _x;
};
class Data
{
public:
explicit Data(int year, int month = 10, int day = 1)
{
cout << "data()" << endl;
}
~Data()
{
cout << "~data()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
A a1 = 2;
Data a2 = 2020;
return 0;
}