既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
丸
\color{red}{鹿九丸}
鹿九丸,今天给大家带来的是类和对象(中)的第一部分,因为这一篇的内容实在是太多了,补充了很多细节,相信大家能够在学习的过程中能够有所收获!
如果大家在看我的博客的过程中或者学习的过程中以及在学习方向上有什么问题或者想跟我交流的话可以加我的企鹅号:2361038962
\color{red}{2361038962}
2361038962,或者寄邮件到相应的邮箱里:
2361038962
@
q
q
.
c
o
m
\color{red}{2361038962@qq.com}
2361038962@qq.com,我会尽量帮大家进行解答!
1. 类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情 况下,都会自动生成下面6个默认成员函数。
class Date {};
学习上面这几个函数分为下面两个大方面:
1、基本语法特性。函数名、参数、返回值、什么时候调用…
2、我们不写编译器默认生成的这个函数干了些什么。
2. 构造函数
2.1 概念
对于下面的日期类:
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, d2; d1.Init(2022, 5, 15); d1.Print(); Date d2; d2.Init(2022, 5, 16); d2.Print(); return 0; }
对于Date类,可以通过SetDate公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信 息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢? 出于这个目的,出现了构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员 都有一个合适的初始值,并且在对象的生命周期内只调用一次。
2.2 特性
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主 要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
- 函数名与类名相同。
- 无返回值。
Date()//函数名和类名相同且没有返回值
{}
- 对象实例化时编译器自动调用对应的构造函数。 (不需要在构造函数前加void,因为我们一旦加上之后,编译器一方面会认为我们定义的是普通的成员函数,但是编译器同时又认为与其类名相同的成员函数一定是构造函数,此时就出现了命名冲突现象,即我们定义的函数名,一定不可与编译器的6个默认成员函数名相同)
- 构造函数可以重载。
class Date
{
public:
//1.无参构造函数
Date()
{
_year = 0;
_month = 0;
_day = 0;
}
//2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//3.全缺省构造函数,作用相当于前面两种构造函数加起来,所以,有了第三种,第一种和第二种就可以都不使用了,所以我们习惯上写这种
Date(int year = 0, int month = 0, int day = 0)
{
_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.Print();
Date d2(2022, 5, 15);//带参
d2.Print();
//问:可不可以这样写?Date d3();
//答:这样是不可以的,因为编译器无法区分这是一个函数声明(函数名为d3,返回类型为Date)还是一个对象定义
return 0;
}
输出结果:(保留第一种和第二种或者只保留第三种的结果)
注意:我们一般在使用构造函数的时候使用全缺省,当然,我们也可以使用半缺省。
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写时编译器默认生成的构造函数,都可以认为是默认成员函数。
注意:并不是我们不写构造函数时系统自动调用的才是默认构造函数,而是我们不需要传参数就能调用的才是默认构造函数。
比如下面的代码中程序就会报错:
class Date
{
public:
Date(int year, int month, int day)
{
_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;
return 0;
}
此时程序就会出现问题,如下图所提示:
因为我们自己定义了构造函数,所以编译器将隐藏或者说不再调用编译器自己的默认构造函数,此时我们再像上面那样去调用默认构造函数程序将无法正确运行了,除非这样进行修改:Date d1(2022, 5, 15);
//三种默认构造函数
//1.没写时编译器默认生成的构造函数
类名()//实际不可见
{
//1.对于内置类型会将其初始化为随机值,
//2.对于自定义类型会调用其构造函数来进行初始化
}
//2.无参构造函数
类名()
{
//可以对成员变量进行初始化
_year = 0;
_month = 0;
_day = 0;
//默认对自定义类型调用其构造函数进行初始化
}
//3.全缺省构造函数
类名(year = 0, month = 0, day = 0)
{
//对成员变量进行初始化
_year = year;
_month = month;
_day = day;
//默认对自定义类型调用其构造函数进行初始化
}
- 关于编译器生成的默认成员函数,很多人会有疑惑:在我们不实现构造函数的情况下,编译器会生成 默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如 int/char…,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数
class Time
{
public:
Time()//Time构造函数
{
_hour = 0;
_minute = 0;
_second = 0;
}
int _hour;
int _minute;
int _second;
};
class Date
{
public:
//当我们不写构造函数的时候,编译器会生成一个默认构造函数
//内置类型/基本类型:int/char/double/指针
//自定义类型:class/struct
//默认生成构造函数对于内置类型成员变量不会做处理,但是对于自定义类型的成员变量就会调用它们的构造函数进行初始化构造
//在下面的这个例子中,因为Date类型的变量中有自定义类型的成员变量\_time,所以会调用\_time即Time类型的构造函数,然后将\_hour、\_minute、\_second初始化为0
//\_year、\_month、\_day都是内置类型,所以不做处理,打印出来是随机数
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
cout << _time._hour << '-' << _time._minute << "-" << _time._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _time;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
经过打印发现输出结果为_year、_month、_day为随机数,而_time._hour、_time._minute、_time._second为0。
打印截图:
总结:一般情况下一个C++类,都要自己写构造函数。一般只有少数情况可以让编译器默认生成。
1、类里面都是自定义类型成员,并且这些成员都提供了默认构造函数。
2、如果还有内置类型成员,声明时给了缺省值,也可以使用默认构造函数,当然这种情况相对来说比较少。
3、使用C++库里的STL数据结构时可以使用编译器自动生成的默认构造函数。
注意:构造函数必须定义在public权限中,否则我们无法在主函数中定义相关的变量,因为如果我们将其放在private权限中,我们在类外是无法访问的,自然也就无法进行定义该种类型的变量。
注意:看下面的例子:
class Time
{
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date()
{
_year = 0;
_month = 0;
_day = 0;
_time._hour = 0;
_time.minute = 0;
_time.second = 0;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
cout << _time._hour << '-' << _time._minute << "-" << _time._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _time;
};
在上面的例子中,我们试图在Date的构造函数中将_time._hour、_time._minute、_time._second进行初始化,但这是无法实现的,因为在Time类中,我们将这三个变量定义为私有权限,在类外无法进行访问,所以自然也就不能在Date的构造函数中进行初始化。
结论:各个类中应当有自己的构造函数来初始化自己的成员变量,而不应该越俎代庖,即使另一个类中定义的成员变量的权限是public,我们也不应该如此去做,因为我们无法保证我们通常使用的类类型的成员变量是public,而且一般来说,成员变量的权限也都是private,这是一个好习惯。
8. 在上面的第6点中,默认构造函数有3种,需要注意的是,无论是三种中的哪一种默认构造函数,都能够对类中定义的自定义类型种的构造函数进行调用,例如下面的例子:
class Time
{
public:
Time()//会自动进行调用
{
_hour = 0;
_minute = 0;
_second = 0;
}
void Print()
{
cout << _hour << "-" << _minute << "-" << _second << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date()//在Date类的构造函数中,我们没有对Time构造函数进行显式调用(当然我们本身也无法进行显式调用),但实际上编译器会默认调用Time的构造函数的
{
_year = 0;
_month = 0;
_day = 0;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
_time.Print();
}
private:
int _year;
int _month;
int _day;
Time _time;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
打印结果:
9. 注意看下面这段代码:
class Time
{
public:
Time(int hour)//自己定义的构造函数,有形参,且不是全缺省,所以不再属于默认构造函数了,所以无法被自动调用
{
_hour = 0;
_minute = 0;
_second = 0;
}
void Print()
{
cout << _hour << "-" << _minute << "-" << _second << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
//没有定义默认构造函数,所以编译器生成默认的构造函数,对内置类型赋随机值,对自定义类型调用其默认构造函数进行初始化
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
_time.Print();
}
private:
int _year;
int _month;
int _day;
Time _time;
};
int main()
{
Date d1;
return 0;
}
此时程序会报错,因为Date类型中的Time类型的变量没有默认构造函数(即我们自己手动定义的构造函数把编译器默认生成的构造函数给掩盖了),所以程序无法通过。那么这种情况下有没有解决方案呢?答案是有的,解决方案就是我们需要初始化列表,后面我们会学到。
10. 成员变量的命名风格:我们一般习惯在成员变量的前面加一个_
,或者在后面加也可以,或者在成员变量的前面加一个m(member的意思)。这是为了防止下面情况的出现:
class Date
{
public:
Date(int year, int month, int day)
{
year = year;//注意:此时的year是形参,而不是成员变量,此时符合就近原则,哪个近是哪个,不是成员对象
month = month;
day = day;
}
private:
int year;
int month;
int day;
Time _time;
};
此时编译器将无法区分year是形参还是Date类中的成员变量了。
当然,我们可以像下面这样定义构造函数来避免上面存在的问题:
class Date
{
public:
Date(int year, int month, int day)
{
this -> year = year;
this -> month = month;
this -> day = day;
}
private:
int year;
int month;
int day;
Time _time;
};
![img](https://img-blog.csdnimg.cn/img_convert/f65a586198c08dd0a2507222b0c33faa.png)
![img](https://img-blog.csdnimg.cn/img_convert/ec96b9c6c3d8202358c6062f748e6170.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
这样定义构造函数来避免上面存在的问题:
class Date
{
public:
Date(int year, int month, int day)
{
this -> year = year;
this -> month = month;
this -> day = day;
}
private:
int year;
int month;
int day;
Time _time;
};
[外链图片转存中…(img-3WAQb5C7-1715541876861)]
[外链图片转存中…(img-6sjtlibp-1715541876861)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新