文章目录
1.构造函数
- 对象初始化:创建对象时,给变量第一次赋值称为对象初始化。
- 构造函数:处理对象初始化问题;构造函数是一种成员函数,无需用户调用,而是建立对象时自动调用匹配的构造函数。
- 构造函数的名字与它所属的类名相同,被声明为公有函数,且没有任何类型的返回值。
- 允许为内联函数、重载函数、带默认形参值的函数。
1.1无参数的构造函数
- 无参数构造函数就是不给构造函数传参
- 无参构造函数可以不写,系统会默认给出无参构造函数
class Student{
private:
int age;
int num;
public:
Student(){ //无参构造函数
age = 0;
num = 0;
}
};
int main(){
Student stu;// age=0 num=0
Student stu(); //错误方式
}
1.2有参数构造函数
- 调用者向构造函数中传入数据
写法一
class Student{
private:
int age;
int num;
public:
Student(int Age,int Num){ //有参数构造函数
age = Age;
num = Num;
}
};
int main(){
Student stu(18,100);// age=18 num=100
}
写法二
class Student{
private:
int age;
int num;
public:
Student(int Age,int Num);
};
Student::Student(int Age,int Num){ //有参数构造函数
age = Age;
num = Num;
}
int main(){
Student stu(18,100);// age=18 num=100
}
1.3缺省构造函数
- 参数列表给出部分数据的值
- 实参的个数不能少于缺省形参的个数
#include <iostream>
using namespace std;
class Date{
private:
int year;
int month;
int day;
public:
Date(int y,int m,int d = 10){ //缺省构造函数
year = y;
month = m;
day = d;
}
void display(){
cout<<year<<" "<<month<<" "<<day<<endl;
}
};
int main(){
Date date(2020,6); //可以不给day传值
date.display();
return 0;
}
1.4用参数初始化列表对数据进行初始化
- 参数初始化表实现对数据成员的初始化,这种方法不在函数体内对数据成员初始化,而是在函数首部实现。
- 注意:若数据成员是数组,则不能在参数初始化列表中对
其初始化,而应在构造函数的函数体重用语句对其赋值。 - 优点:需要初始化的参数过多时,使用该方法可以减少代码量。
class Date{
private:
int year;
int month;
int day;
public:
Date(int y,int m,int d);
};
Date::Date(int y,int m,int d):year(y),month(m),day(d)
{
}
int main(){
Date(2020,6,1);//year=2020 month=6 day=1
return 0;
}
1.4构造函数重载
- 构造函数重载:在一个类中定义多个构造函数,这些构造函数的函数名相同,参数个数或参数类型不相同,称为构造函数的重载。
class Student{
private:
int age;
int num;
public:
//以下两个构造函数构成构造函数的重载
Student(){ //无参构造函数
age = 0;
num = 0;
}
Student(int Age,int Num);
};
Student::Student(int Age,int Num){ //有参数构造函数
age = Age;
num = Num;
}
int main(){
Student stu1; //自动调用无参构造函数:age=0 num=0
Student stu2(18,100);//自动调用有参构造函数age=18 num=100
}
2.析构函数
-
析构函数:作用与构造函数相反,但不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。
-
一个类可以有多个构造函数,但只能有一个析构函数
-
执行析构函数的情况:
- 用new运算符动态地建立对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
- 在一个函数中定义了一个局部对象,当这个函数调用结束时,在对象释放前自动执行析构函数。
- 如果定义了一个全局对象,只有在程序结束时,才调用该全局对象的析构函数。
- 静态(static)局部对象只在main函数结束时,才调用static局部对象的析构函数
- 如果用户未定义析构函数,C++编译器也会自定生成一个析构函数,但是它只有析构函数的名称和形式,实际上什么操作也不做。
3.构造函数与析构函数的调用顺序
- 函数调用存在函数压栈,函数调用结束存在函数弹栈
- 先构造的后析构,后构造的先析构
图解
4.对象数组
- 数组不仅可以由简单变量组成,也可以由类对象组成
- 对象数组的每一个元素都是一个同类的对象
#include <iostream>
using namespace std;
class Date{
private:
int year;
int month;
int day;
public:
Date(){
}
Date(int y,int m,int d = 10){
year = y;
month = m;
day = d;
}
void display(){
cout<<year<<" "<<month<<" "<<day<<endl;
}
};
int main(){
Date dates[3] = {Date(1,1,1), //第一个元素调用有参构造函数赋值
Date(2,2,2),//第二个元素调用有参构造函数赋值
Date(3,3,3)//第三个元素调用有参构造函数赋值
};
int i;
for(i = 0; i < 3;i++){
dates[i].display();
}
return 0;
}
5.对象指针
class Date{
private:
int year;
int month;
int day;
public:
Date(int y,int m,int d = 10){ //构造函数
year = y;
month = m;
day = d;
}
void display(){
cout<<year<<" "<<month<<" "<<day<<endl;
}
};
int main(){
Date date(2020,6,1); //定义Date类对象
Date *pd; //定义Date类型对象指针
pd = &date; //指针指向Date类对象
pd->display();//使用对象指针间接调用成员函数
(*pd).display();
}
6.this指针
- 在每一个成员函数中都包含一个this指针,它是指向本对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。
- this指针常用来分辨形参与实参
class Date{
private:
int year;
int month;
int day;
public:
Date(int year,int month,int day = 10){ //构造函数
//下面的this不可省略,用以区分形参和实参
this->year = year;
this->month = month;
this->day = day;
}
void display(){
cout<<this->year<<" "<<this->month<<" "<<this->day<<endl;
//此处的this可以省略
}
};
6.const关键字
- 要使数据能在一定范围内共享,又要保证它不被任意修改,这时可以把有关的数据定义为常量。
6.1常对象
- 用const修饰对象
- 格式1:类名 const 对象名【(实参列表)】;
- 格式2:const 类名 对象名【(实参列表)】;
Date const date1(2020,6,1);
const Date date2(2019,6,1);
date1.display(); //非法调用普通成员函数
说明:
- 常对象必须初始化
- 常对象只能调用常成员函数,无法调用普通常成员函数
- 常成员函数是常对象对外的唯一接口
- 常对象无法修改内部数据成员的值
有时编程时有要求,如果执意要通过常对象修改内部数据成员的值,可以通过将需要修改的属性声明为mutable,这样就可以通过常对象来修改属性值
mutable int year;
mutable int month;
6.2常成员变量
- 用const修饰类内部某属性,该属性只能通过构造函数的参数初始化表来初始化,任何其他函数都不能对常数据成员赋值
- 可以理解为:const修饰的成员变量只能赋值一次
class Time{
private:
const int hour;
const int minute;
const int sec;
public:
Time(int h,int m,int s):hour(h),minute(m),sec(s){}//合法
Time(int h,int m,int s){
//非法,该构造函数无法对const修饰的成员变量进行初始化
hour = h;
minute = m;
sec = s;
}
};
6.3常成员函数
- 格式:类型名 函数名(参数表) const{ }
#include <iostream>
using namespace std;
class Time{
private:
int hour;
int minute;
int sec;
public:
Time(int hour,int minute,int sec){
this->hour = hour;
this->minute = minute;
this->sec = sec;
}
void doOther(){
cout<<"hello world!"<<endl;
}
void doSome()const{
cout<<hour<<":"<<minute<<":"<<sec<<endl;
//合法,可以引用属性
hour = 6;//非法,无法修改属性
doOther();//非法,无法调用非const成员函数
}
};
- 常成员函数可以引用成员变量,无法修改成员变量
- 常成员函数无法调用另一个非const成员函数
6.4指向对象的常指针
- 将指针变量声明为const型,这样指针变量的指向始终不能改变
- 格式:类名 *const 指针变量名;
Time t1(1,2,3),t2(4,5,6);
Time* const pt; //定义Time类型的常指针
pt = &t1; //合法初始化
pt = &t2; //非法改变常指针指向
6.5指向常对象的指针变量
- 格式:const 类名 * 指针变量名;
- 常变量只能用指向常变量的指针变量来指向;常对象也只能用常对象的指针变量来指向;
- 指向常对象的指针变量可以指向非const修饰的普通对象
- 指向常对象的指针变量的指向可以随意更改
- 无法通过指向常对象的指针变量来修改对象内部的值
class A{
public:
int x;
int y;
A(int x,int y){
this->x = x;
this->y = y;
}
};
void doSome(const A *p){//形参为指向常对象的指针变量
p->x = 20; //非法,无法通过指向常对象的指针变量来修改对象内部的值
p->y = 20; //非法,无法通过指向常对象的指针变量来修改对象内部的值
}
void doOther(A *p){//普通A类型的指针
p->x = 20; //合法
p->y = 20; //合法
}
int main(){
A a(10,10);
doSome(&a);
doOther(&a);
return 0;
}
6.6对象的常引用
- 将对象的引用用const修饰起来,则无法通过引用来修改对象内部的属性值
- 格式: 类名 &引用名 = 对象名;
class A{
public:
int x;
int y;
A(int x,int y){
this->x = x;
this->y = y;
}
};
int main(){
A a(10,10);
const A &b = a; //定义并初始化常引用
b.x = 20; //非法,无法通过常引用修改对象值
b.y = 20; //非法,无法通过常引用修改对象值
a.x = 20; //合法
a.y = 20; //合法
return 0;
}
6.7const总结
7.对象的建立与释放
- 用new运算符动态地分配内存建立对象,用delete运算符释放内存撤销对象
Date *pd = new Date(2020,6,1); //对象指针指向动态创建的一个无名对象
delete pd; //释放对象
8.对象的赋值与复制
8.1对象的赋值
- 使用"="可以实现用对象给对象进行赋值
- 赋值是针对对象中的成员变量的,而不是针对成员函数的;因为成员变量是每个对象单独一份代码,而成员函数是所有对象共用一份代码
Date date1(2020,1,6);
Date date2 = date1;
//此时date2中的数据值和date1中的数据值一模一样
//date1和date2是两个完全不同的对象
8.2对象的复制
- 复制构造函数:作用是将一个对象的各个成员值一一赋给新的对象中对应的成员
- 如果用户未定义复制构造函数,则系统会自动提供一个默认的复制构造函数,作用是简单的复制类中的每个成员变量
class Student{
private:
int age;
int num;
public:
Student(int age,int num){ //普通构造函数
this->age = age;
this->num = num;
}
Student(Student &s){ //复制构造函数
age = s.age;
num = s.num;
}
};
int main(){
Student stu1(10,20); //调用有参构造函数
Student stu2(stu1); //调用复制构造函数
return 0;
}
-
调用复制构造函数的3种情况:
-
1.需要建立一个新对象时
Student stu2(stu1);
- 2.函数参数为类的对象时
void fun(Student stu){}
int main(){
Student stu(1,1);
fun(stu); //传入的对象便是stu的一份拷贝
}
- 3.函数返回值是类的对象时
Student fun(){
Student s;
return s; //返回值是Student类的对象
}
int main(){
Student s;
s = fun();
return 0;
}
9.static关键字
9.1静态成员变量
- static修饰的成员变量并不只属于某一个对象,所有对象都可以引用它
- 静态成员变量在内存中只占一份空间
- 静态成员变量的存在是不依赖于对象的,它是属于类的
- 在一个对象中改变了静态成员变量,那么静态成员变量的值在所有对象中都改变了
- 简单来说,所有对象共享一份静态成员变量
class Box{
private:
static int length; //静态成员变量
int height;
int long;
};
int Box::length = 100; //初始化静态成员变量
- 静态成员变量只能在类外进行初始化
- 初始化格式: 数据类型 类名::静态成员变量名 = 初值
- 不能用参数初始化表对静态数据成员初始化
可以通过类来单独访问静态成员变量
- 类访问格式:
Box::length = 10;
9.2静态成员函数
- static修饰的成员函数属于类而不属于对象
- static修饰的成员函数没有this指针,所以静态成员函数无法访问本类中的非静态成员变量
- 可以通过类名和对象两种方法调用函数
class A{
public:
static void fun(){}
};
int main(){
A a;
a.fun(); //通过对象调用静态成员函数
A::fun(); //通过类调用成员函数
return 0;
}
10.友元(friend)
在类外可以访问公有成员,只有本类中的成员函数可以访问本类中的私有成员,友元提供了一个从外部访问类内部私有成员的方式。
10.1友元函数
- 在本类以外定义一个函数,在类体内部声明为友元函数,此函数就称为本类的友元函数,此友元函数就可以访问这个类中的私有成员。
- 一个函数可以被多个类声明为“朋友”,这样就可以引用多个类中的私有数据
10.1.1将普通函数声明为友元函数
#include <iostream>
using namespace std;
class Date{
private:
int year;
int month;
int day;
public:
Date(){
year = 0;
month = 0;
day = 0;
}
friend void display(Date &date);
};
void display(Date &date){
//将此函数声明为友元函数,就可以访问类内部私有属性
cout<<date.year<<" "<<date.month<<" "<<date.day<<endl;
}
int main(){
Date d;
display(d);
return 0;
}
10.1.2友元成员函数
- 在Time类中将Date类中的display方法声明为友元函数,这样Date类中的display方法就可以无障碍访问Time类中的私有属性
#include <iostream>
using namespace std;
class Time;
class Date{
private:
int year;
int month;
int day;
public:
Date(int year, int month, int day){
this->year = year;
this->month = month;
this->day = day;
}
void display(Time &t);
};
class Time{
private:
int hour;
int minute;
int sec;
public:
Time(int hour, int minute, int sec){
this->hour = hour;
this->minute = minute;
this->sec = sec;
}
friend void Date::display(Time &);
//将Date中的display方法在Time中声明为友元函数,便可以访问
//Date中的私有属性
};
void Date::display(Time &t){
cout << year << " " << month << " " << day << " "
<< t.hour << ":" << t.minute << ":" << t.sec << endl;
//声明为友元函数便可以访问Time类中的私有属性
}
int main(){
Time t(1, 1, 1);
Date d(2, 2, 2);
d.display(t);
system("pause");
return 0;
}
11.类模板
-
若多个类其功能相同,仅仅是数据类型不同,可以声明一个通用的类模板,它可以有一个或多个虚拟的类型参数。
-
定义类模板格式:template <class 虚拟类型名>
-
用类模板定义对象格式:
类模板<实际类型名> 对象名;
类模板<实际类型名> 对象名(实参列表);
例如:要实现一个类可以比较不同类型的2个数字的大小
template <class T>
class Compare{
private:
T x;
T y;
public:
Compare(T x,T y){
this->x = x;
this->y = y;
}
T max(){
return (x>y)?x:y;
}
T min(){
return (x<y)?x:y;
}
};
int main(){
Compare<int> compInt(5,6); //比较int型数据
compInt.max();
compInt.min();
Compare<float> compFloat(5.3,6.3); //比较float型数据
compFloat.max();
compFloat.min();
Compare<double> compDouble(6.6,7.4); //比较double型数据
compDouble.max();
compDouble.min();
return 0;
}