文章目录
类的默认成员函数
1构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值 ,并且在对象的生命周期内只调用一次。
1.1构造函数特征
构造函数是特殊的成员函数,其主要的功能是初始化对象。特征:
- 1.函数名和类名相同
- 2.无返回值,且返回类型不能写void
- 3.对象实例化时编译器自动调用对应的构造函数
- 4.构造函数可以重载
class Date
{
public:
//无参数构造函数
Date()
{
}
//带参构造函数
Date(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;//调用无参数构造函数
d1.print();
Date d2(2022,5,15);//调用带参数的构造函数
d2.print();
//需要注意的是,调用无参数构造函数时,对象后面不能加上括号,否则编译器会认为是函数声明
// Date d3(); 这种写法存在歧义
return 0;
}
1.2编译器自动生成的构造函数
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
class Date
{
public:
//编译器会自动生成一个无参的构造函数
void print()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.print();
return 0;
}
1.3编译器默认的构造函数
构造函数把成员变量分为两种类型
- 内置类型/基本类型:int/char/double/ptr…
- 自定义类型class/struct定义的类型对象。
- 无参的构造函数、全缺省的构造函数、用户不写编译器默认生成的构造函数都被称为默认构造函数
- 默认生成的构造函数对应内置类型成员变量不做处理,对于自定义类型成员变量才会处理
- 无参的构造函数和全缺省的构造函数只能存在一个,否则会发生在构造对象时会出现歧义。
上面的可能不太好理解,我们用下面的程序来解释
程序1
class Date
{
public:
Date()
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
该程序编译无法通过,构造d1时编译器不知道调用无参的构造函数还是全缺省的构造函数
程序2
class A
{
public:
A()
{
cout << " A()" << endl;
_a = 0;
}
private:
int _a;
};
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
A _aa;
};
int main()
{
Date d1;
return 0;
}
程序3
class A
{
public:
A(int a)
{
_a = a;
}
private:
int _a;
};
class Date
{
public:
private:
A _aa;
A _bb;
};
int main()
{
Date d1;
A ca;
return 0;
}
Date成员变量是两个A类型的变量,所以Date的默认构造函数是需要A调用自己的构造函数
而A定义了带参的构造函数,不存在默认构造函数,所以Date的默认构造函数无法被调用。
//修改方式
class A
{
public:
//或者A()或者A(int a=10)
private:
int _a;
};
class Date
{
public:
private:
A _aa;
A _bb;
};
int main()
{
Date d1;
A ca;
return 0;
}
1.4C++11特征
在C++发展的过程中,有些大佬也发现只处理自定义类型的成员变量不方便,为了向下兼容又要对缺点进行改正,C++的大佬们打了个补丁
class A
{
public:
A(){
_a = 0;
}
private:
int _a;
};
class Date
{
public:
private:
A _aa;
A _bb;
int _ca=10;
};
int main()
{
Date d1;
return 0;
}
2析构函数
与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的资源清理工作
2.1特征
- 1.析构函数名是在类名前加上字符~
- 2.无参数返回值,会构造函数一样
- 3.一个类有且只有一个析构函数:因为析构函数无参数所以无法进行函数重载
- 4.对象生命周期结束时,C++编译器自动调用析构函数
class arr
{
public:
arr(int capacity=10)
{
_a=(int*)malloc(sizeof(int)*capacity);
assert(_a);
_capacity=capacity;
_size=0;
}
~arr()
{
cout<<"free()"<<endl;
free(_a);
_a=nullptr;
_capacity=_size=0;
}
private:
int _capacity;
int _size=0;
int* _a;
};
2.2编译器默认的析构函数
和构造函数一样,析构函数也把成员变量分为两种类型;对于内置类型析构函数不做处理,自定义类型析构函数会调用自定义类型的默认析构函数。
class arrs
{
public:
arrs(int sizes = 2)
{
_size= sizes;
}
private:
arr ar1;
arr ar2;
int _size;
};
int main()
{
arrs arrs1;
return 0;
}
析构函数的调用顺序
int main()
{
arr arr1(5);
arr arr2(5);
return 0;
}
函数栈帧是在虚拟进程地址空间中的栈区建立,满足先创建后销毁的规则
3拷贝构造函数
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
3.1特征
- 拷贝构造函数是构造函数的重载形式
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
关于传值方式会引发无穷递归调用,我也是想了一段时间才理清楚调用的方式和传递过程:
**C++中规定,对于自定义类型传参需要先进行拷贝构造,而拷贝构造需要传递参数…**不断的套娃,出现死递归
//如果使用传值传参
class Date
{
public:
Date(int year=2022,int month=5,int day=15)
{
_year=year;
_month=month;
_day=day;
}
Date(Date date)
{
_year=date._year;
_month=date._month;
_day=date._date;
}
private:
int _year;
int _month;
int _day;
}
//正确写法
class Date
{
public:
Date(int year=2022,int month=5,int day=15)
{
_year=year;
_month=month;
_day=day;
}
Date(const Date&date)
{
_year=date._year;
_month=date._month;
_day=date._date;
}
private:
int _year;
int _month;
int _day;
}
3.2编译器默认生成的拷贝构造函数
若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝方式为浅拷贝。
class Date
{
public:
Date(int year=2022,int month=5,int day=15)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2021,5,16);
Date d2(d1);
return 0;
}
浅拷贝的方式缺点是只适合对不包含指针的对象进行拷贝
class stack
{
public:
stack(int capacity = 10)
{
_a = (int*)malloc(sizeof(int) * capacity);
assert(_a);
_capacity = capacity;
_top = 0;
}
~stack()
{
cout << "~stack()" << endl;
free(_a);
_a = nullptr;
_capacity = _top = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
int main()
{
stack st1;
stack st2(st1);
return 0;
}
原因:如果使用编译器默认的拷贝构造函数,那么默认的拷贝构造函数对象按内存存储按字节序完成拷贝。
而stack类有int _a成员变量,在浅拷贝中,只是把str1._a指针传递给了str2._a。所以两个变量的指针是相同的!!!*
因此两次析构函数释放的是同一个空间,所以会出现运行错误
题目:
设已经有A,B,C,D4个类的定义,程序中A,B,C,D析构函数调用顺序为?( )
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}
解析:因为d是static静态变量,c是全局变量,两者都存放在静态区;在main函数调用完被销毁;又因为函数栈帧在栈区,所以顺序为 b,a,d,c