1.构造函数是什么?
构造函数负责为类的成员进行初始化,并完成一系列其他相关功能。
构造函数可以放在类里面和类外面,格式不同而已
类声明结束后,像结构体一样创建变量也就是对象的时候,会先调用一次构造函数,然后创建对象,并且是必须调用构造函数
既然类创建对象必须先调用构造函数,那岂不是类里面必须要有构造函数?
是的,就算一个类里面没有写构造函数,编译器会自动帮你写一个默认构造函数
那么为什么C++要创建对象前要调用一次函数呢,这不是多此一举很奇怪吗
我们先看场景,构造函数是在创建对象之前调用,那么肯定是预防对象的成员在创建之后遇到的问题,构造函数干脆在他们创建之前直接解决了,比如在栈空间创建对象,对象的变量成员是垃圾值,用构造函数在创建他们之前进行初始化和赋值,就避免他们在栈空间创建后出现垃圾值的问题
可是我有感觉有时候构造函数没有初始化或者赋值,感觉非常得多此一举
这种感觉是对的,因为构造函数不会强制你进行初始化和赋值或者进行某个操作,导致这个构造函数没有发挥作用,是这样的
C++也允许这样
当你的类很简单,成员都是 int、char 这种基础类型,初始化只是简单清零。这种场景 C++ 会给你隐式默认构造函数,你不写代码,编译器自动生成,你感知不到它的存在。一旦你的类有动态内存、文件句柄等资源,构造函数的价值立刻体现。
构造函数需要自己去自定义里面的参数列表和代码块
默认函数也算自定义函数的一种,只不过参数和代码块都是空,没有影响而已
自定义构造函数就是在类里面按着自己的想法给成员初始化,默认构造函数就是你不写构造函数的时候编译器自己帮你写的
构造函数的函数格式非常特殊,构造函数一般放在public里面
构造函数在类内部,有权访问本类所有成员,不管是 public 公有,还是 private 私有!
构造函数的语法格式:
自定义构造函数语法格式:
自定义构造函数不能写返回值类型,函数名就是类名,参数列表可以有参数也可以没有
类名(int a,int b) //构造函数强制必须没有返回值类型,不能写void!!!
{ //构造函数的函数名就是对应类名
成员1初始化;
成员2初始化;
}
默认构造函数语法格式:
默认构造寒素的语法格式就是没有参数和代码块内容的自定义构造函数
类名() //默认函数不能有参数和代码块内容
{
}
当你不写构造函数的时候编译器自动帮你写默认构造函数,当然你也可以自己按着标准写默认构造也可以
默认构造函数包括编译器自动帮你写的和你自己按着标准写的
构造函数的分类
构造函数分为有参数构造函数,无参数构造函数,默认构造函数
有参数里面又有拷贝构造函数
我还喜欢把构造函数分为自定义构造函数和非自定义构造函数
自定义构造函数主要是用来判断类是不是聚合类,这样类可不可以用结构体间接定义变量花括号初始化的方法初始化,聚合类只能直接在声明处花括号后面定义变量
构造函数用法:
构造函数总共六种用法
用法1:
构造函数无参数
当然默认构造函数也没有参数
class student
{
public:
int age ;
int sno;
student();
{
age=10; //自定义构造函数给成员赋值
sno=10086;
name="bllue"; //构造函数的功能给公有和私有都可以
cout //自定义构造函数也可以完成一系列其他的功能
}
private:
string name;
int func()
{
cout
return 0;
}
};
int main()
{
student a; //此时只要类创建了对象,对应构造函数就调用了 ,所以此时已经赋值和打印cout了
//类创建对象的格式是按照构造函数,这里构造函数没有参数,就变量名后面没括号就行
//但是构造函数没有参数调用时候可以不写括号,其他函数调用没有参数还是要写括号
//当然变量名后面没括号也可以是默认构造函数,因为他也没有括号
//此时完成类的对象创建,就是像结构体的间接定义变量,结构体数据类型多一个关键字struct
return 0;
}
用法2:
构造函数有参数
有参数时候自然没有默认构造函数了
class student
{
public:
int age ;
int sno;
student(int a,string b); //此时构造函数里面有参数,函数有参数无非就只是用参数的值罢了
{
age=a; //自定义构造函数给成员赋值
sno=10086;
name=b;
cout //自定义构造函数也可以完成一系列其他的功能
}
private:
string name;
int func()
{
cout
return 0;
}
};
int main()
{
studnet a(18,"red"); //类创建对象并非一尘不变,类创建对象的格式是按照构造函数的格式
//类创建对象就是调用构造函数,把变量名想象成构造函数名,有参数就后面写括号,按着实参传参就行
return 0;
}
用法3:
构造函数重载
构造函数也遵循函数重载,所以构造函数重载是函数名相同,参数列表不同
构造函数重载就是一个类里面可以不止有一个构造函数,可以有参数可以没有参数
放多个构造函数必须遵循函数重载,构造函数的灵魂就是函数名
多个构造函数书写时候还是按着原来语法格式,不用去区分
class student
{
public:
int age ;
int sno;
student(int a); //此时第一个构造函数里面有参数
{
age=a;
sno=10086;
name=b;
cout
}
student(int cc,string dd); //此时第二个构造函数里面也有参数
{
age=c;
sno=10086;
name=d;
cout
}
student() //此时第三个构造函数没有参数
{
age=100;
sno=10086;
name="blue"
cout
}
private:
string name;
int func()
{
cout
return 0;
}
};
int main()
{
studnet a(18,"red"); //类创建对象并非一尘不变,类创建对象的格式是按照构造函数的格式
//类创建对象就是调用构造函数,把变量名想象成构造函数名,有参数就后面写括号,按着实参传参就行
studnet a(18,"red"); //此时就是调用第二个构造函数,输出第二个构造函数的代码
student a(20); //此时就是调用第二个构造函数,输出第二个构造函数的代码
return 0;
}
用法4:
构造函数放在类外面
构造函数放在类外面语法格式会不一样
构造函数虽然强制不能写void,但其实还是一个函数,不能看作不是函数了
我们平时写构造函数都是写她的函数定义
构造函数放在类外面只有两个位置,第一个是main函数上面,就是原本类的下面,第二个是main函数的下面
构造函数放在main函数上面,构造函数又是函数定义,自然主函数调用没有问题
但是构造函数放在main下面,先是main构造函数调用,肯定不可以,函数调用之前必须声明或者定义
此时构造函数,函数定义放在main下,所以类里面必须写这个构造函数的声明
(1)构造函数放在类外面,但在main函数上面(最常用)
class student
{
public:
int age ;
int sno;
private:
string name;
int func()
{
cout
return 0;
}
};
student::student(int a,string b) //其实就是类在外面访问自己的函数而已,只不过这个函数刚好是函数定义
{ //此时这个就是函数定义
age=a;
string=”blue";
}
int main()
{
studnet a(18,"red"); //其余一样还是该传参就传参 //此时这个就是函数调用
return 0;
}
(2)构造函数放在类外面,但在main函数上面(最常用)
class student
{
public:
int age ;
int sno;
student(int a,string b); //构造函数的声明,构造函数的声明最好放公有
//因为构造函数声明放私有,外面访问不到这个构造函数的声明自然不能调用
//类创建对象必须构造函数可以调用,构造函数调用不了类无法创建对象
private:
string name;
int func()
{
cout
return 0;
}
};
int main()
{
studnet a(18,"red"); //其余一样还是该传参就传参
return 0;
}
student::student(int a,string b) //此时函数定义放在函数调用后面
{
age=a;
string=”blue";
}
用法5:
构造函数搭配初始化列表初始化 (推荐,重点!)
就是在构造函数括号右边加一个初始化列表,但是这个初始化列表初始化的值只能是参数里面的值
初始化列表在构造函数函数体 {} 执行之前完成成员初始化,比函数体内赋值效率更高,优先级更早。
但函数体里面的赋值还是能改变最终的值
初始化列表的变量初始化顺序不是看参数是看对应定义变量的顺序
类里面不区分公有私有,初始化列表的变量里面可以有私有,还是按着从上到下的顺序就行
#include<iostream> #include<string> using namespace std; class student { public: int age; //这里才是定义变量的顺序,这里是先age再name,初始化列表也要按照这个顺序 int sno; int func() { cout<<"hello world"<<endl; return 0; } //初始化列表的变量初始化顺序必须是定义变量的顺序 student(int a,string b):age(a),name(b) //此时是用初始化列表初始化,age的值只能拿形参里的a的值,name的值是b { //此时函数体不用再初始化了,如果函数体有赋值,赋值会覆盖上面的初始化 //age=30;//此时age还是等于30,赋值会覆盖上面的初始化 cout<<"hello world1"<<endl; cout<<"age="<<age<<endl; cout<<"name="<<name<<endl; } private: string name; }; int main() { student a1(10,"red"); return 0; }student(int a,string b):age(a),name(b) //:age(a),name(b) 就叫初始化列表,这里是初始化,定义变量可以和初始化分开,这里的
//变量没有等于符号就是初始化
{ //此时函数体不用再初始化了,如果函数体有赋值,赋值会覆盖上面的初始化
//age=30;//此时age还是等于30,赋值会覆盖上面的初始化
cout cout cout
}
用法6:
构造函数是默认参数搭配初始化列表初始化 (推荐,重点!)
当遇到参数是默认参数时候,函数调用语法格式要变化
student(int a=30,string b="blue"):age(a),name(b) //就是长这样的,实参如果没改变形参,那么初始化就是形参值
{
//age=30;//此时age还是等于30,赋值会覆盖上面的初始化
cout cout cout
}
int main()
{
//函数有默认参数调用函数的三种方式,这里的默认参数格式是和创建对象一起的
student s1; //默认参数没括号表示是默认的,不是代表没有参数
student s2(10); //默认参数里面实参的值优先级大于形参默认值
student s3(10,"red");
return 0;
}