目录
一.类的组合
1.以另一个类为数据类型
注意:构造函数必须采用初始化参数列表的写法!且要用分支类的构造函数的写法,举例说明:
class A
{
public:
A(string name,int age):name(name),age(age){}
protected:
string name;
int age;
};
class B
{
public:
B(string name, int age, int year) :a(name, age), year(year){}
protected:
int year;
A a;
};
可以看到,类B中要初始化数据类型A时,也调用了A的构造函数。注意,这里的意思不是说以另一个类为数据类型,所有的参数都要用初始化参数列表的形式初始化,只是分支类的初始化需要,所以我们也可以这样写:
B(string name, int age, int year) :a(name, age)
{
this->year = year;
}
那B类中的A的访问权限限定还存在吗?答案是肯定的,来看以下代码:我们在A设置了两个功能相同的打印函数,一个放在public,一个放在protected,看一下他们有什么不同:
class A
{
public:
A(string name,int age):name(name),age(age){}
void printData1()
{
cout << name << " " << age << endl;
}
protected:
void printData2()
{
cout << name << " " << age << endl;
}
string name;
int age;
};
class B
{
public:
B(string name, int age, int year) :a(name, age)
{
this->year = year;
}
void printB()
{
a.printData1();
a.printData2(); //没有权限,无法访问
cout << year << endl;
}
protected:
int year;
A a;
};
int main()
{
B b("小蓝", 18, 7);
b.printB();
}
可以看到,我们打出来的时候编译器报错显示无法访问,因为A的对象a对于A来说是属于类外的,不可直接访问保护属性下的数据成员、成员函数,a对象调用A的属性也受权限限定,因此要在public中提供接口来访问。
2.构造与析构顺序问题
前面我们分析了一些常见的构造与析构顺序问题的分析,详情可见:构造函数与析构函数
今天我们补充一点,那就是类的组合中的构造与析构的顺序问题,同样的,我们来分析下面的代码:
class A
{
public:
A(string name):name(name)
{
cout << "我构造了A" << endl;
}
~A()
{
cout << "我析构了A" << endl;
}
protected:
string name;
};
class B
{
public:
B(string name) :name(name)
{
cout << "我构造了B" << endl;
}
~B()
{
cout << "我析构了B" << endl;
}
protected:
string name;
};
class C
{
public:
C(string name) :name(name)
{
cout << "我构造了C" << endl;
}
~C()
{
cout << "我析构了C" << endl;
}
protected:
string name;
};
class D
{
public:
D(string aname,string bname,string cname) :b(bname),c(cname),a(aname)
{
cout << "我构造了D" << endl;
}
~D()
{
cout << "我析构了D" << endl;
}
protected:
A a;
B b;
C c;
};
int main()
{
D d("A", "B", "C");
return 0;
}
最后的打印结果是:
可以看到,构造的顺序跟初始化参数列表的顺序是没有关系的,构造的顺序跟定义数据的顺序有关,我们先定义了ABC,然后构造出这三个之后最后构造出一个包含他们三个的完整对象d,之前说过,正常情况下析构顺序与构造顺序相反,这里也同样适用,所以是 “DCBA” 。
二.类中类
类中类跟类的组合不同,类中类的意思是有一个类的定义是在类中的,而类的组合只是使用了类当数据成员。类中类的访问依然受权限限定,且访问方式需要类名限定。
我们以手写一个简单的迭代器(用类模仿指针的行为)的方式举例,来遍历链表中的数据:
第一步:写好链表的基本结构。
//之前说过,结构体跟类的使用是相关的,由于数据都是共有的,所以用结构体写
struct Node
{
int data;
Node* next;
Node() //无参数以创建表头
{
this->next = nullptr;
}
Node(int data) //有参数来创建节点
{
this->data = data;
this->next = nullptr;
}
};
第二步:在List类中写好链表的基本操作,然后创建迭代器的小框架。
class List
{
public:
List() //创建链表
{
headNode = new Node;
}
void push_head(int data) //头插法
{
Node* newNode =new Node(data);
newNode->next = headNode->next;
headNode->next = newNode;
}
protected:
Node* headNode;
public:
class iterator //迭代器
{
public:
protected:
};
};
第三步:补充迭代器中的操作,主要是对一些运算符的重载。
class List
{
public:
List() //创建链表
{
headNode = new Node;
}
void push_head(int data) //头插法
{
Node* newNode =new Node(data);
newNode->next = headNode->next;
headNode->next = newNode;
}
protected:
Node* headNode;
public:
class iterator //迭代器
{
public:
iterator(Node* pMove = nullptr):pMove(pMove){}
//实现两个指针的赋值操作
void operator=(Node* object)
{
pMove = object;
}
//两个指针之间指向的不等
bool operator!=(Node* object)
{
return pMove != object;
}
//指针++前置操作重载,返回的是对象
iterator operator++()
{
pMove = pMove->next;
return iterator(pMove);
}
Node*& operator*()
{
return pMove;
}
protected:
Node* pMove;
};
//获取开始的位置
Node* begin()
{
return headNode->next;
}
//获取结束的位置
Node* end()
{
return nullptr;
}
};
第四步:输入验证代码。
int main()
{
List list;
for (int i = 0; i < 5; i++)
{
list.push_head(i);
}
List::iterator iter;
for (iter = list.begin(); iter != list.end(); ++iter)
{
cout << (*iter)->data << endl;
}
}
输出如下:
可以看到,出现了我们想要的打印结果。
三.类中的枚举及默认函数
1.类中的枚举
同样,类中的枚举依然受访问权限的限制。
class A
{
public:
enum time { first, second };
protected:
enum date { one, two, three }; //类中的枚举类型受权限限定
};
int main()
{
one; //直接访问报错
cout << A::one; //受权限限定报错
cout << A::first;//没报错
}
2.类中默认的函数
class A
{
public:
A() = default; //构造函数
A(A& object) = default; //拷贝构造函数
A& operator=(A& object) = default; //返回类型:A类对象的引用 赋值的重载
~A() = default; //析构函数
};
注意:普通函数是没有默认的函数的。
好了,今天的总结就到这,下篇更新C++类的继承的用法!