类
类用来实现函数封装
类是用来描述"对象"的, 类包括属性以及行为, 如同人就是一个类, 属性即包括人的性格、品格、样貌等, 人的动作表现则属于类的行为(行为一般为函数)。而具体的某一个人则是类的实例——对象。
类是对象的模板,对象是类的实例。
类是一个封装了数据以及操作这些数据的代码的逻辑实体
类的三种权限:
公共权限 (public) :类外可以访问, 类内可以访问
保护权限 (protected) : 类外不可以访问, 类内可以访问,对派生类可见
私有权限(private): 类外不可以访问, 类内可以访问
class 类名
{
//公共访问权限
public:
//属性和行为
//保护访问权限
protected:
// 属性和行为
//私有访问权限
private:
//属性和行为
};
class和struct的区别为默认权限不同
在类(class)中,未声明成员的属性,则默认是 private
在结构体(struct)中,默认是public
this指针
类的成员函数可以访问类自身的数据,那么如何知道哪一个对象的数据要被成员函数操作?
通过this指针来访问自己的地址,this指针指向被调用成员函数所属的对象
例如student.show();则student就是对象,this指针就指向它
this 是一个指针,要用-> 来访问成员变量或成员函数。
注意:this指针并不是对象的一部分,this指针所占的内存大小是不会反应在sizeof操作符上的。
this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。
1、this指针的类型取决于使用this指针的成员函数类型以及对象类型
2、this指针只能在成员函数中使用,全局函数、静态函数都不能使用
? 成员函数默认第一个参数为T* const register this。
返回*this表示返回对象本身
3、this 只能用在类的内部,通过 this 可以访问类的所有成员,包括 private、protected、public 属性的
const关键字
const函数
cosnt修饰的成员函数:const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值,这种措施主要还是为了保护数据而设置的。
常成员函数需要在声明和定义的时候在函数头部的结尾加上 const 关键字
const对象
const 也可以用来修饰对象,称为常对象。一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)
静态成员
静态成员变量
静态成员要在类内声明,类外初始化。
静态成员变量不属于任何对象,静态成员变量是由类的所有对象共享的
静态成员变量只存储一份供所有对象共用,所以在所有对象中都可以共享它的值,使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。
class A {
public:
static int a;//静态成员变量声明即在变量的数据类型前加static
};
int A::a = 0;
int main() {
A temp0;
temp0.a = 5;//静态成员变量需要定义,即为静态成员变量分配空间
A temp1;
cout << temp0.a << endl;
cout << temp1.a << endl;
}
//两行输出结果皆为5,说明静态成员变量共享
静态成员变量在const函数中可以被修改
cosnt函数的作用是保护调用这个const函数的对象(即this指针指向的对象)的所有普通成员变量,使其不能被修改,但是静态成员变量不属于任何对象,所以可以修改。
class A {
public:
static int a;
int b;
void changea(int i) const {
this->a = i;//this指针访问静态成员变量a
}
};
int A::a;
int main() {
A temp0;
temp0.changea(6);
}
静态成员函数
,静态成员函数也是属于类的,它并不属于任何对象。静态成员函数中是不能访问本类中的非静态成员变量的,因为非静态成员函数只有在类对象建立以后才可以调用。所以在c++中,静态成员函数的主要功能就是访问静态成员变量
class A {
public:
static int a;
int b;
//在函数前加上static即将函数声明为静态成员函数
static void showa(){
cout << a << endl;
}
/*static void showb() {
cout << b << endl;
}*/
//这段被注释的代码会报错
};
int A::a = 9;
int main() {
A temp0;
temp0.showa();
}
友元
友元提供了一种 普通函数或者类成员函数 访问另一个类中的私有或保护成员 的机制。也就是说有两种形式的友元:友元函数和友元类
优点:提高了程序的运行效率。
缺点:破坏了类的封装性和数据的透明性。
友元函数
普通函数对一个访问某个类中的私有或保护成员。在类的任何区域声明, 在类外进行定义
格式:friend 类型 友元函数名 (参数);
友元类
friend class <友元类名>;
- 单向性:类A申明类B是它的友元,B能使用A的私有成员;但是A不是B的友元,不能使用B的私有成员。
- 友元不能被继承:类A申明类B是它的友元,A的儿子(继承于A)跟B不是友元。
- 友元不具有传递性:类A是B的友元,类B是C的友元,但是C不一定是A的友元。
例如,以下语句说明类B是类A的友元类:* class A { … public: friend class B; … }; 经过以上说明后,类B的所有成员函数都是类A的友元函数,能存取类A的私有成员和保护成员。
对象的初始化和清理
构造函数
主要作用在于创建对象时为对象的成员属性初始化,
通过一个或者几个特殊的成员函数来控制其对象的初始化过程
总与new运算符一同使用
语法:类名(){}
1、构造函数,没有返回值, 不写void
2、函数名与类名相同
3、构造函数可以有参数, 可以发生重载
4、程序在调用对象时候会自动调用构造函数, 无须手动调用, 且只会调用一次
构造函数的分类和调用
1、分类
按参数分类:有参数构造与无参数构造
按类型分类:普通构造和拷贝构造
拷贝构造函数常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象,把对象作为参数传递给函数,从而使用对象的所有属性。
- 复制对象,并从函数返回这个对象。
格式:classname (const classname &obj) {
? // 构造函数的主体
? }
2、调用
(1)括号法
person p1;//默认构造函数调用(注:调用默认构造函数时不要加括号,因为编译器会将其识别为一个声明)
person p2(10);//有参构造函数调用
person p3(p4);//拷贝构造函数调用
(2)显示法
person p1 = person(10);//有参构造函数调用。本质为匿名对象,当前执行结束后会释放匿名对象
person p2 = person(p1);//拷贝构造函数调用。(不要利用拷贝构造函数初始化匿名对象)
(3)隐式构造
person p1 = 10;//相当于person p1(10)
person p2 = p1;//相当于person p2(p1)
浅拷贝与深拷贝
浅拷贝:简单的赋值操作,由编译器提供
深拷贝:在堆区开辟一个新的空间,进行拷贝。
二者区别:
如果仅拷贝对象的属性值,浅拷贝与深拷贝没有区别都,会将原有对象复制一份,产生一个新对象,对新对象里的值进行修改不会影响原有对象,新对象和原对象完全分离开。
如果拷贝的对象里的元素包含引用(像一个列表里储存着另一个列表,存的就是另一个列表的引用),那浅拷贝和深拷贝是不同的,浅拷贝虽然将原有对象复制一份,但是依然保存的是引用,所以对新对象里的引用里的值进行修改,依然会改变原对象里的列表的值,新对象和原对象并没有完全分离开。而深拷贝则不同,它会将原对象里的引用也新创建一个,即新建一个列表,然后放的是新列表的引用,这样就可以将新对象和原对象完全分离开
所以浅拷贝和深拷贝只针对引用类型
析构函数
主要作用在于对象销毁前系统自动调用, 执行一些清理工作。
语法 :~类名(){}
1、析构函数, 没有返回值,不写void
2、函数名与类名相同
3、析构函数不能有参数
4、对象在销毁前会自动调用析构函数, 无需手动调用, 且只会调用一次
运算符重载
对已有运算符j重新进行定义,赋予其另一种功能,以适应不同的数据类型
重载+运算符
形如:一个类中有两个属性m_A和m_B,创建两个成员,并将两个成员相加时
类成员函数person operator+(person &p),其中一个成员属性要利用this指针来访问
全局函数person operator+(person &p1, person &p2)
类成员函数和全局函数的区别就是,一个是面向对象,一个是面向过程
class person
{
public:
int m_A;
int m_B;
person operator+(person &p)//通过成员函数重载+
{
person temp;
temp.m_A = this->m_A + p.m_A;//用this指针访问a1的属性
temp.m_B = this->m_B + p.m_B;
return temp;
}
};
//通过全局函数重载+
person operator+(person &p1, person &p2)
{
person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
person a1, a2;
a1.m_A = 5;
a2.m_A = 5;
a1.m_B = 10;
a2.m_B = 10;
person a3 = a1 + a2;//将两个成员相加
重载<<运算符
作用:直接输出成员函数
创建全局函数ostream& operator<<(ostream &cout, person &p)
#include <iostream>
using namespace std;
class person
{
public:
int a;
int b;
};
//使用引用,使用了引用就是别名,不会开辟新的内存空间,自然也不会释放,返回对象
ostream &operator<<(ostream &cout, person &p)//cout的返回类型为ostream
{
cout << p.a << " " << p.b;//链式思想
return cout;
}
int main(void)
{
person m;
m.a = 10;
m.b = 12;
cout << m << endl;
system("pause");
return 0;
}
重载++运算符
使成员完成++操作。
#include <iostream>
using namespace std;
class box
{
friend ostream &operator<<(ostream &cout, box myprint); //友元
public:
box()
{
a = 0;
}
//前置++,先递增,再返回成员
box &operator++()
{
a++;
return *this; //返回引用是为了一直对一个数据进行递增操作
}
//后置++,先记录,再递增,返回记录的值
box operator++(int) //int为占位参数,发生函数重载
{
box temp = *this;
a++;
return temp; //局部变量在当前函数执行完后被释放,不能返回引用,需返回值
}
private:
int a;
};
//重载<<运算符
ostream &operator<<(ostream &cout, box myprint)
{
cout << myprint.a;
return cout;
}
//后置递增
void text01()
{
box m;
cout << m++ << endl;
cout << m << endl;
}
//前置递增
void text02()
{
box m;
cout << ++m << endl;
cout << m << endl;
}
int main(void)
{
text01();
text02();
system("pause");
return 0;
}
重载=运算符
对于类中有属性指向堆区会出现浅拷贝和深拷贝问题。
对于浅拷贝(错误示例)
//浅拷贝错误示例
//目的:将m拷贝到n上
#include <iostream>
using namespace std;
class box
{
public:
box(int age)
{
A = new int(age);
}
~box()//析构函数
{
if (A != NULL)
{
delete A;
A = NULL;
}
}
int *A; //类中属性在堆区
};
void text01()
{
box m(18);
box n(20);
n = m;
cout << *m.A << " " << *n.A << endl;
}
int main(void)
{
text01();
system("pause");
return 0;
}
此状态下将发生错误,原因在于析构函数。进行析构函数时,会先将n的堆区清除,再将m的堆区清除,由于浅拷贝,成员m,n共有同一块内存,该内存进行了两次清除所以程序出现错误。
**解决方法:**利用深拷贝思想,重载赋值运算符,为成员n开辟另一个堆区,使两个成员的堆区相互独立。
实现如下:
#include <iostream>
using namespace std;
class box
{
public:
box(int age)
{
A = new int(age);
}
~box()
{
if (A != NULL)
{
delete A;
A = NULL;
}
}
box &operator=(box &p)
{
//先判断是否堆区内有属性,如果有,则先清除
if (A != NULL)
{
delete A;
A = NULL;
}
A = new int(*p.A);//重新为n开辟一个堆区
return *this;//链式编程思想,以实现连续拷贝情况
}
int *A; //类中属性在堆区
};
void text01()
{
box m(18);
box n(20);
n = m;
cout << *m.A << " " << *n.A << endl;
}
int main(void)
{
text01();
system("pause");
return 0;
}
重载关系运算符
#include <iostream>
#include <string>
using namespace std;
class person
{
public:
person(string name, int age)
{
this->age = age;
this->name = name;
}
//重载==运算符
bool operator==(person &p)
{
if (this->age == p.age)
return true;
else
return false;
}
//重载!=运算符
bool operator!=(person &p)
{
if (this->age != p.age)
return true;
else
return false;
}
string name;
int age;
};
void test01()
{
person p1("Tom", 18);
person p2("Tom", 19);
if (p1 == p2)
cout << "same" << endl;
if (p1 != p2)
cout << "no" << endl;
}
int main(void)
{
test01();
system("pause");
return 0;
}
重载()运算符(仿函数)
()称为函数调用运算符
仿函数灵活,没有特定的写法
#include <iostream>
#include <string>
using namespace std;
//仿函数不是函数,是类
class person
{
public:
//仿写输出函数
void operator()(string s)
{
cout << s << endl;
}
};
void test01()
{
person myprint;
myprint("hello world");
person()("hello world");//在不创建指定对象时,创建匿名函数对象
}
int main(void)
{
test01();
system("pause");
return 0;
}
|