「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」
💦 概念
❗ 场景 ❕
class Stack
{
public:
void Init()
{
_a = nullptr;
_top = _capacity = 0;
}
void Push(int x);
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack s1;
s1.Push(3);
return 0;
}
📝 说明
C 语言中,可能会忘记调用 Init 函数,然后一顿错误操作。这时 C++ 就能做到对象定义出来就初始化 —— 构造函数。
可以认为构造函数的出现就是为了替代 Init。
❗ 利用构造函数替代 Init ❕
class Stack
{
public:
Stack()//1.函数名和类名相同//2.无返回值
{
_a = nullptr;
_top = _capacity = 0;
}
Stack(int capacity)
{
_a = malloc(sizeof(int)*capacity);
if(_a == nullptr)
{
cout << "malloc fail" << endl;
exit(-1);
}
_top = 0;
_capacity = capacity;
}
void Push(int x);
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
Stack s1;//3.对象实例化时编译器自动调用对应的构造函数
Stack s2(10);//4.构造函数可以重载
s1.Push(3);
return 0;
}
📝 说明
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
💦 特性
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
其特征如下:
1️⃣ 函数名与类名相同。
2️⃣ 无返回值。
3️⃣ 对象实例化时编译器自动调用对应的构造函数。
4️⃣ 构造函数可以重载。
接下来我们就来理解几个概念用 ❓ ❔ 包含
❓ 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成 ❔
class A
{
public:
A()
{
_a1 = 0;
_a2 = 1;
cout << "A()" << endl;
}
private:
int _a1;
int _a2;
};
class Date
{
public:
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
//内置类型/基本类型:int、char、指针 ... ———— 不会初始化
int _year;
int _month;
int _day;
//自定义类型:struct/class ———— 调用它的无参构造函数初始化
A _a;
};
int main()
{
Date d1;//对象实例化时自动调用构造函数
d1.Print();//再调用Print函数
return 0;
}
📝 说明:
不是说我们不写构造函数,编译器会自动生成一个无参的默认构造函数 ❓
这也是早期 C++ 设计留下的一个缺陷,有些编译器会把这初始化为 0,1,1
C++ 把我们的变量分成内置类型 (int、char、指针 ...) 和自定义类型 (struct/class)
我们不写构造函数,编译器默认生成构造函数,但是编译器做了一个偏心的处理
1、内置类型不会初始化 2、自定义类型它会调用它的无参构造函数初始化
其实这块内容算是 C++ 在早期设计时的语法缺陷,这种偏心的处理导致语法反而复杂了,后面也发现了这个问题,但是为时已晚不能修改,因为 C++ 要向前兼容。好多书上也没有讲清楚,这也是很多人说 C++ 难学的地方。
关于编译器生成的默认构造函数,很多童鞋会有疑惑:在我们不实现构造函数的情况下,编译器会生成默认构造函数,但看起来默认构造函数又没什么用 ❓
这里说看起来没什么用主要体现在 d1 调用了编译器生成的默认构造函数,但是 _year、_month、_day 依然是随机值。
C++ 把类型分成内置类型 (基本类型) 和自定义类型,这里只是说它不会处理内置类型,而自定义类型它会去调用它的无参构造函数
到了 C++11 时 C++ 针对这里的情况做了补充 ❓
C++11 时,语法委员会针对这里打了一个补丁,也就是在变量声明中加上缺省值。
这个补丁雀实会给很多初学者造成困扰,很多人会认为它是初始化,其实它是缺省值。
class A
{
public:
A()
{
cout << "A()" << endl;
}
private:
int _a1;
int _a2;
};
class Date
{
public:
Date()
{
_year = 2022;//只初始化_year,其它默认
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
//C++11 缺省值
int _year = 0;
int _month = 1;
int _day = 1;
A _a;
};
int main()
{
Date d1;
d1.Print();
return 0;
}
📝 说明
就同函数的缺省参数一样。如果构造函数里没有初始化,那么对于内置类型它会使用缺省值初始化
❓ 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数 (不传参数就可以调用的那个函数) ❔
class Date
{
public:
Date()
{
_year = 0;
_month = 1;
_day = 1;
}
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//Date d1(3);ok
return 0;
}
📝 说明
如上两个构造函数 Date 本来是可以构造函数重载的,但是他们不能同时存在 ~ Why ?❓
因为编译器调用的时候会出现歧义,一般情况下我们推荐写一个全缺省的构造函数,因为它比较好用。
这里有一个误区:有的童鞋会认为,我们不写,编译器默认生成的构造函数就叫做构造函数,这个是不对的,它还有无参的构造函数和全缺省的构造函数都是默认构造函数。
❓ 成员变量的命名风格 ❔
class Date
{
public:
Date(int year)//不好的命名风格
{
year = year;
}
Date(int year,int month)//好的命名风格
{
_year = year;
_month = month;
}
private:
int year;
int _year;
int _month;
};
int main()
{
Date d1(1);
Date d2(1, 2);
return 0;
}
📝 说明
当然这样不仅仅是规范的问题,还可能会造成运行时错误。
执行 Date d1 (1) ; 后,year 的值是 1 还是随机值 ❓
站在编译器的角度 this->year = year,所以是 1 ???
NO ~~,实际上在 Date 里它会采用就进原则,也可以认为是局部优先。
当 year = year 时,它会先找到形参,然后就变成了自己赋值给自己了。
当 _year = year 时,它先找局部变量,没找到,它会再外面的域搜索,找到了,编译器就会在前面加 this。
或者直接 this->year = year 但不推荐。