目录
定义类
成员函数有两种定义方式:
成员变量名的建议:
访问限定符
类的作用域
类的实例化
类对象的存储方式
this指针
this指针的特性
this指针可以为空吗
类的默认成员函数
构造函数——初始化
初始化列表
默认构造函数
构造函数的调用
析构函数——成员销毁(析构是按照构造的相反顺序进行析构,static变量最后销毁)
默认析构函数
拷贝构造函数
默认构造拷贝函数
拷贝构造函数典型调用场景
赋值运算符重载
运算符重载
赋值运算符重载是类成员函数
前置++和后置++
<< 和 >> 重载
const修饰成员函数
explicit修饰构造函数
static成员
定义
特性
友元函数
定义类
//class是定义类的关键字 name是类名
class name
{
//类体:由成员函数(类方法)和成员变量(类属性)组成
};
C语言中的结构体只能定义变量,但是在C++中还可以定义函数,这是因为C++将结构体上升为类
struct Person
{
char _name[10];
int _age;
char _sex[5];
int _score;
void ShowInfo()
{
cout << _name << endl;
cout << _sex << endl;
cout <<_age << endl;
cout << _score << endl;
}
};
成员函数有两种定义方式:
- 声明和定义全部放在类体中(函数可能被编译器当作内联函数处理)
- 类的定义和函数声明放在的头文件中,函数的定义放在源文件中,成员函数名前要+类名和" :: "
//Person.h
class Person
{
char _name[10];
int _age;
char _sex[5];
int _score;
void ShowInfo();
};
//Person.cpp
void Person::ShowInfo()
{
cout << _name << endl;
cout << _sex << endl;
cout <<_age << endl;
cout << _score << endl;
}
成员变量名的建议:
class date
{
private:
int _day;
int _mouth;
int _year;
public:
void Year(int year)
{
_year = year;
}
void Mouth(int mouth)
{
_mouth = mouth;
}
void Day(int day)
{
_day = day;
};
函数的形参与成员变量同名,赋值时很难分清二者是谁,所以推荐在成员变量名加修饰
访问限定符
- public修饰的成员可以在类外被访问
- privata和protected修饰的成员不可在类外被访问
- 访问限定符的作用域是从此限定符开始到下一个限定符结束,如果后面没有访问限定符,那么到类的 } 结束
- 为了和C兼容,struct被升级为类后,所有的成员默认都是public;class默认都是private
类的作用域
类成员所在的区域都在类的作用域,当在类外定义成员函数时,需要使用 :: (作用域限定符)指明成员属于哪个作用域
class date
{
private:
int _day;
int _mouth;
int _year;
public:
void Year(int year);
};
void date::Year(int year)
{
_year = year;
}
类的实例化
创建类对象的过程就是类实例化的过程
把类比作一张建筑模型图,类的实例化就像按照图纸完成这个建筑的建造。类就是对对象的描述,在内存中并没有分配空间
一个类可以创建多个对象,每个对象都会占用空间
class date
{
private:
int _day;
int _mouth;
int _year;
public:
void Year(int year)
{
_year = year;
}
void Mouth(int mouth)
{
_mouth = mouth;
}
void Day(int day)
{
_day = day;
}
void Show()
{
cout << _year << endl << _mouth << endl << _day << endl;
}
};
int main()
{
date today;
today.Year(2023);
today.Mouth(4);
today.Day(21);
today.Show();
return 0;
}
类对象的存储方式
class date
{
private:
int _day;
int _mouth;
int _year;
public:
void Year(int year);
void Mouth(int mouth);
void Day(int day);
void Show();
};
int main()
{
date today;
cout << sizeof(today) << endl; //12
return 0;
}
类对象只保存成员变量,成员函数保存在公共代码段,不计入对象的大小。所以类成员大小就是成员变量按照内存对齐原则计算得到的结果
如果一个类没有成员变量,那么实例化的成员大小是1字节,只是为了占位,表示对象存在,不存储有效数据。
this指针
既然成员函数不存在对象的空间中,那当不同的对象调用相同的函数时,函数是怎么识别具体对哪一个对象做出改变的呢?原来,C++编译器为每一个成员函数增加了一个隐藏的指针参数,让指针指向现在调用此函数的对象,对象的改变都是通过对这个指针操作完成的,只不过传参的过程对用户是透明的,由编译器自动完成
this指针的特性
- this指针的类型:类类型* const,成员函数不能对this赋值
- 只能在函数内部使用
- this指针的本质是成员函数的形参(存储在栈中),当对象调用函数时,会将对象的地址作为实参传递给this,所以对象中不存储this形参
- this指针时成员函数的第一个隐藏形参,一般情况由编译器通过ecx寄存器传递,不需要用户传递
this指针可以为空吗
当我们通过对象调用函数时,this一定不为空;当用指针调用函数时可能为空
class date
{
private:
int _day;
int _mouth;
int _year;
public:
void Show()
{
cout << "Show()" << endl;
}//函数未使用this访问成员变量,this是空指针也可以运行
void Show()
{
cout << _year << endl; //this->_year
}//函数通过this访问了成员变量,对空指针的访问会发成错误
};
int main()
{
date* today = nullptr;
today->Show();
return 0;
}
类的默认成员函数
用户没有显示实现,编译器会生成的成员函数叫默认成员函数
class data{};
像这样,一个类中什么都没有的类叫空类,那空类中什么都没有吗?当然不是,编译器会自动生成一些默认成员函数:
常用的有上面四种:
构造函数——初始化
构造函数是一种特殊的成员函数,函数名与类名相同,创建类的对象时自动调用,使对象的成员变量有合适的初始值,并且在对象的生命周期内只能调用一次
特点:
- 函数体前有初始化列表用来初始化
- 函数名与类名相同
- 无返回值(不是void)
- 对象实例化时编译器自动调用构造函数
- 构造函数可以重载
初始化列表
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括
号中的初始值或表达式。初始化列表是构造函数的一部分
注意:文章来源地址https://www.toymoban.com/news/detail-467838.html
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
引用成员变量
const成员变量
自定义类型成员(类没有默认构造函数时必须初始化,如果有默认构造会自动调用可以不写)3.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
成员变量初始化只在初始化列表中完成,类中的成员变量只是声明,声明变量给的默认值就是初始化列表的缺省值。函数体内可以为变量赋值,但不是初始化。
class Stack
{
private;
//只是变量的声明
int _capacity = 40;
int _top = 0;
int* _a;
public:
Stack()
//初始化列表
:_a( (int*)malloc(sizeof(int)*_capacity) )
,_top(0)
,_capacity(capacity)
//函数体
{
if(nullptr == _a)
{
perror("malloc fail\n");
}
}
};
默认构造函数
用户不定义实现构造函数的情况下,编译器会自动生成默认构造函数(无参)。C++规定自动生成的默认构造函数不对内置类型(基本类型)初始化,对于自定义类型成员,会调用它的成员构造函数。
注:
C++11 中针对内置类型成员不初始化的缺陷,做了优化,即:内置类型成员变量在类中声明时可以给默认值,这其实是初始化列表中成员变量的缺省值。
构造函数的调用
1.无参构造函数
class date
{
private:
int _day;
int _mouth;
int _year;
public:
date()
:_day(1)
,_mouth(1)
_year(2023)
{}
};
int main()
{
date today;
return 0;
}
//注意 :
//当构造函数无参或者是全缺省参数时,对象实例化调用构造函数时不需要加"()",
//因为: date today(); 也可以认为是函数声明,
//编译器无法辨别
2.全缺省构造函数
class date
{
private:
int _day;
int _mouth;
int _year;
public:
date(int day = 1, int mouth = 1, int year = 2023)
{
_day = day;
_mouth = mouth;
_year = year;
}
};
int main()
{
date today;
return 0;
}
3.带参构造函数
class date
{
private:
int _day;
int _mouth;
int _year;
public:
date(int day, int mouth, int year = 2023)
:_day = day
,_mouth = mouth
,_year = year
{}
};
int main()
{
date today(22, 4, 2023);
return 0;
}
//注意:
//使用 data today; 会报错,因为用户定义了构造函数
//编译器不会生成无参的默认构造函数,对象实例化时,
//必须要给初始值
无参构造函数和全缺省构造函数不能同时定义,虽然它们构成函数重载,但是调用它们时,函数的实例与多个形参列表匹配,造成对重载函数调用不明确的问题
析构函数——成员销毁(析构是按照构造的相反顺序进行析构,static变量最后销毁)
特点:
- 析构函数名是在类名前加~
- 析构函数无参无返回值类型
- 析构函数未定义,会自动生成。析构函数不能重载
- 对象的生命周期结束,系统会自动调用析构函数
默认析构函数
用户没有定义析构函数,系统自动生成的析构函数,它对内置类型不做处理,最后由系统统一回收即可;对自定义类型,会调用它自己的析构函数
class Data
{
private:
int _year;
int _mouth;
int _day;
public:
Data(int year = 2023, int mouth = 0, int day = 0)
{
_year = year;
_mouth = mouth;
_day = day;
cout << "Data()" << endl;
}
~Data()
{
cout << "~Data()" << endl;
}
};
class Stack
{
private:
int* arr;
int capacity;
int top;
Data today;
public:
Stack(int init_capacity = 4)
{
arr = (int*)malloc(sizeof(int) * init_capacity);
if (arr == NULL)
{
perror("malloc fail");
return;
}
capacity = init_capacity;
top = 0;
cout << "Stack()" << endl;
}
~Stack()
{
free(arr);
top = 0;
capacity = 0;
cout << "~Stack()" << endl;
}
};
int main()
{
Stack s;
return 0;
}
//运行结果:
//Data()
//Stack()
//~Stack()
//~Data()
拷贝构造函数
特点:
- 拷贝构造函数是构造函数的重载函数
- 拷贝构造函数的参数只有一个,且是该类类型的引用(使用传值会造成无穷递归)
无穷递归的原因:
C++规定,内置类型传参为浅拷贝(直接传值),自定义类型传参是深拷贝。所以第一次调用拷贝构造时,实参与形参间会调用拷贝构造,而这个构造的形参的深拷贝还会再次调用构造,依此类推无穷无尽。
默认构造拷贝函数
未显式定义,系统自动生成的默认拷贝构造函数,对内置类型会进行浅拷贝(值拷贝),对自定义类型会调用自己的拷贝构造函数
class Data
{
private:
int _year;
int _mouth;
int _day;
public:
Data(int year = 2023, int mouth = 0, int day = 0)
{
_year = year;
_mouth = mouth;
_day = day;
}
};
int main()
{
Data d1(2023, 4, 26);
Data d2(d1);
return 0;
}
既然系统自动生成的拷贝构造函数可以对内置类型拷贝,我们还有必要写吗?当然有必要了,看下面的例子:
class Stack
{
private:
int* _arr;
int _capacity;
int _top;
public:
Stack(int capacity = 4)
{
_arr = (int*)malloc(sizeof(int) * capacity);
_capacity = capacity;
_top = 0;
}
Stack(const Stack& s)
{
_arr = (int*)malloc(sizeof(int) * s._capacity);
_capacity = s._capacity;
_top = s._top;
}
~Stack()
{
free(arr);
top = 0;
capacity = 0;
cout << "~Stack()" << endl;
}
};
int main()
{
Data d1(2023, 4, 26);
Data d2(d1);
return 0;
}
Stack的成员_arr是指针类型,初始化d1时_arr向内存动态申请了空间。如果不写拷贝构造函数,在拷贝时不会为新对象d2的_arr分配一块空间,而是把d1的内容浅拷贝给d2,在对象生命周期结束后,析构函数会对指向同一块空间(d1的_arr和d2的_arr)的指针做两次内存释放,就会造成错误。
拷贝构造函数典型调用场景
- 使用已存在对象创建新对象
- 函数参数类型为类类型对象
函数形参对实参的拷贝会调用拷贝构造函数
- 函数返回值类型为类类型对象
函数要返回的类对象是由拷贝构造函数拷贝到另一个类对象的
例子:
class Date
{
private:
int _year;
int _mouth;
int _day;
public:
Date(int year = 2023, int mouth = 1, int day = 1)
{
_year = year;
_mouth = mouth;
_day = day;
cout << "Data(int int int)" << endl;
}
Date(const Date& d)
{
_year = d._year;
_mouth = d._mouth;
_day = d._day;
cout << "Date(const Data& d)" << endl;
}
~Date()
{
cout << "~Date()" << endl;
}
};
Date copy(const Date d)
{
Date tmp(d);
return tmp;
}
int main()
{
Date d(2023, 5, 1);
copy(d);
return 0;
}
//运行结果:
//Data(int int int)
//Date(const Data& d)
//Date(const Data& d)
//Date(const Data& d)
//~Date()
//~Date()
//~Date()
//~Date()
分析:
1.创建Date的对象会调用构造函数
2.调用copy函数时,传参会调用拷贝构造函数
3.copy函数内创建一个对象,通过调用拷贝构造函数来初始化
4.copy函数返回的是一个临时Date对象,该对象是用tmp通过拷贝构造函数构造的
赋值运算符重载
运算符重载
运算符重载是具有特殊函数名的函数,函数名为:operator+要重载的运算符符号
函数格式:返回值类型 operator运算符 (参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .*(指向成员的指针运算符) ::(域作用限定符) sizeof ?: . 以上5个运算符不能重载
class Date
{
friend bool operator== (const Date& d1, const Date& d);//友元函数
int _year;
int _mouth;
int _day;
};
bool operator== (const Date& d1, const Date& d2)
{
return d2._year == d1._year && d2._mouth == d1._mouth && d2._day == d1._day;
}
int main()
{
Date d(2023, 5, 1);
Date p(d);
//cout << operator==(d, p) << endl;
cout << (d == p) << endl;
//两种调用方法都可以
return 0;
}
因为成员变量是私有的,在类外无法访问,为了保证类的封装性,不能将成员变量设置为公有,可以使用友元函数解决,本文会在后面详细介绍友元函数,但是友元函数会破坏封装性,我们一般会将运算符重载写到类中。
class Date
{
private:
int _year;
int _mouth;
int _day;
public:
bool operator== (const Date& d)
{
return _year == d._year && _mouth == d._mouth && _day == d._day;
}
};
赋值运算符重载是类成员函数
格式:
- 参数使用类的引用类型
- 参数返回类型使用类的引用类型
- 检查是否自己给自己赋值
- 返回 *this
class Date
{
private:
int _year;
int _mouth;
int _day;
public:
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_mouth = d._mouth;
_day = d._day;
}
return *this;
}
};
注意:
- 赋值运算符重载必须写成成员函数,因为如果赋值运算符重载没有在类内显式实现,系统会自动生成,就会和类外的重载函数冲突。
- 对于内置类型函数是直接赋值的,对于自定义类型会调用其赋值运算符重载函数
- 系统生成的默认赋值运算符重载函数,只会以值的方式逐字节拷贝,和拷贝构造函数类似,当遇到动态内存管理时,就会发生错误,必须要用户自己实现函数
前置++和后置++
//这段代码只是演示++的重载,真正意义实现日期类的++稍显复杂,这里不做演示
class Date
{
private:
int _year;
int _mouth;
int _day;
public:
//前置++
Date& operator++()
{
_day++;
return *this;
}
//后置++
Date operator++(int)
{
Date tmp(*this);
_day++;
return tmp;
}
//tmp是临时对象出了函数就会销毁,不能返回引用
};
前置++和后置++都是一元操作符,为了区分重载函数,C++规定:后置++多加一个int类型的参数,但是调用函数时由系统自动传递。
<< 和 >> 重载
class Date
{
private:
int _year;
int _month;
int _day;
public:
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
};
ostream& operator<<(ostream& out, const Date& d)
{
if (d._month <= 0 ||
d._month > 12 ||
d._day <= 0 ||
d._day > Date::GetMonthDay(d._year,d._month))
{
out << "日期错误" << endl;
}
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
int year, month, day;
cin >> year >> month >> day;
if (month <= 0 || month > 12 || day <= 0 || day > Date::GetMonthDay(year, month))
{
cout << "输入错误" << endl;
}
d._year = year;
d._month = month;
d._day = day;
return in;
}
int main()
{
Date d1;
Date d2;
cin << d1 << d2;
cout << d1 << d2;
return 0;
}
<<和>>操作符的重载一般有两种形式:
- 在类外定义,把它设置为友元函数。因为类内定义,函数的第一个参数只能是隐含的this指针,调用时只能是d.operator<<(cout)或者d<<cout,这样看起来非常奇怪,不符合使用习惯
- 在类外定义,类内设置公共的get方法访问成员变量。
const修饰成员函数
const修饰成员函数,const实际上是对函数第一个参数this的修饰,使得函数不能对类的成员做修改。
void Print()const ->void Print(const Date* this)
建议将所有不需要改变类成员的函数都使用const修饰,因为const修饰的参数可以同时接收被const和不被const修饰的成员
explicit修饰构造函数
当构造函数只有一个参数或者除了第一个参数都是缺省参数时,用一个这个参数类型的变量给对象赋值会发生类型转换:编译器先用变量创建一个匿名对象,再用匿名对象赋值
class Date
{
private:
//...
public:
Date(int n) //Date(int n1,int n2 = 0,...,int nn = 0)
{
//...
}
};
int main()
{
Date d = 10;
//Date d = Date(10);
}
但是,如果单参构造函数被explicit修饰,就没有了类型转换的作用,这样的代码就不会通过。
static成员
定义
用static修饰的成员变量称为静态成员变量,静态成员函数一定要在类外初始化;用static修饰的额成员函数称为静态成员函数。
特性
- 静态成员为所有类对象共享,不属于某个具体对象,存放在静态区
- 静态成员可以用类名::静态成员 或者 对象名.静态成员访问
- 静态成员也是类成员,受访问限定符的限制
- 静态成员变量必须在类外定义,定义时不用写static关键字,类中只是声明
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
友元函数
友元函数可以直接访问类的私有成员,友元函数是定义在类外面的普通函数,不属于任何类,只需要在类内部声明时+friend关键字修饰
前面在写<< >>运算符重载时,要使用友元函数,就是因为类成员函数第一个参数会被this指针占用,而在<< >>的使用中,习惯上将cout和cin放在第一个参数的位置上。
匿名对象
对象创建时没有对象名,直接给出初始化列表的对象就是匿名对象。文章来源:https://www.toymoban.com/news/detail-467838.html
注意:
- 无参构造,类名必须加括号,要与非匿名对象创建区分
- 匿名对象即用即销毁,它的作用域仅在当前行
- 匿名对象赋值给引用,会延长该对象的生命周期,Date& pd = Date d()
到了这里,关于C++——类和对象的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!