0%

C++_类

定义抽象数据类型

定义一个类

C++中可以使用struct和class构建一个类,struct为继承C语言留下,但用法与C不同。struct和class使用方法完全相同,只是对类成员默认的访问权限不同,这点后面会谈到。

1
2
3
4
5
6
struct/class person
{
string name;
string addr;
int test();
}

在类的外部定义成员函数

C++的struct与C最不同的一点就是C++可以在类中定义函数,例如上述类中声明了一个返回值为int型的函数。

该函数的内容可以在类中定义,也可以只在类中声明,再在外部定义。在外部定义时需注意以下几点:

  • 函数类型声明、函数名、参数列表必须和声明中一致
  • 由于函数是在类中声明,在定义时需声明其命名空间(声明函数名)
1
2
int person::test()
{}

定义一个返回类的函数

1
2
3
4
5
6
7
8
9
10
struct person
{
string name;
string addr;
person& copy(const person &x)
{
name=x.name;
return *this;
}
}
  • 该函数同样可以在类外定义,规则同上一节

  • 函数返回的类型必须为引用,因为返回的值应该为一个左值

  • 若函数参数也为一个类对象,也必须为引用格式

  • return *this意为返回该调用该函数的对象,例如:

    1
    2
    3
    4
    person test1,test2;
    test1.name="dhk"; test2.name="aca";
    test1.copy(test2);
    cout<<test1.name; //输出"aca"
  • 函数体中的name和addr为调用该函数的对象的类成员

定义类相关的非成员函数

通过定义流的引用直接操作类成员的输入输出

1
2
3
4
5
6
7
8
9
10
11
istream &read(istream &in, person &item)
{
in>>item.name>>item.addr;
return in;
}
ostream &print(ostream &out, person &item)
{
out<<"NAME: "<<item.name<<endl;
out<<"ADDR: "<<item.addr<<endl;
return out;
}
  • istream和ostream需提前声明其命名空间
  • IO类不能被拷贝,因此当其作为参数时必须通过引用传递值
  • 此处print函数的参数item可以不使用引用

类的构造函数

当我们定义一个类对象时,并没有对每一个类成员进行初始化,这时,编译器便通过一个特殊的构造函数来控制当前初始化,此函数叫做默认构造函数,该函数会对所有成员进行默认初始化。

但是如果我们想在定义类对象时就对一些成员进行我们想要的初始化该怎么办呢?这时我们就可以自己定义构造函数对一些类成员进行想要的初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct person
{
person()=default;
person(string s1, string s2) {name=s1;addr=s2;}
person(person &item) {copy(item);}
person(istream &in) {read(in,*this);}
/**********************以上为构造函数*******************/
string name;
string addr;
person& copy(const person &x)
{
name=x.name;
return *this;
}
}
istream &read(istream &in, person &item)
{
in>>item.name>>item.addr;
return in;
}

int main()
{
person a;
string s1("dhk"),s2("aca"); person b(s1,s2);
person c(b);
person d(cin);
}

在person类中一共定义了四个构造函数,下面将分别讲解这四个构造函数的含义和相关知识:

  • person()=default;默认构造函数

    当一个类中定义了其他构造函数时,编译器便不会自动生成默认构造函数。因此,若没有此函数,主函数中24行便会报错,原因是没有与当前定义情况匹配的构造函数。

  • person(string s1, string s2)

    如主函数25行,当初始化参数为两个string对象时,会进入该构造函数,该构造函数的作用是将类成员赋值为两个string对象。需要注意:

    • 大括号内为构造函数的函数体,可以在此完成一系列的初始化

    • 这里也有一种构造初始值列表的初始化方式:

      1
      person(string s1, string s2): name(s1),addr(s2) {}

      两种方式作用完全相同,但是实现的原理有所差异,这点后面会讲到。

  • person(person &item)

    如主函数26行,类初始化的参数也可以为一个类对象,该构造函数会直接将输入参数用于执行copy函数,相当于通过copy函数间接给类成员赋值。

  • person(istream &in)

    如27行,输入参数为一个IO类型的引用,需要注意此时read函数第二个参数为*this

构造函数初始值列表和赋值的区别

之前我们讲到了构造函数对类成员的操作有两种:

1
2
person(string s1, string s2) {name=s1;addr=s2;}
person(string s1, string s2): name(s1),addr(s2) {}

两个操作在效果上一样,但是第一种操作根源上为赋值,而第二种操作才是初始化,在大部分情况下二者区别不大但是如果当类成员为引用或者const类型时,此时将不能赋值,但是可以初始化:

1
2
3
4
5
6
7
struct person
{
person(int i) {a=i;r=i} //错误的,不能给引用和常量赋值
person(int i): a(i),r(i) {} //正确
const int a;
int &r;
}

访问控制和封装

前面讲到了使用class和struct唯一区别是对类成员默认的访问权限不同,C++中可以使用访问说明符来管理类成员的访问权限以达到更好的封装。如果使用struct,则在第一个访问说明符前的成员默认为public,class则相反。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class person
{
public: //以下成员在整个程序中都可以被访问
person()=default;
person(string s1, string s2) {name=s1;addr=s2;}
person(person &item) {copy(item);}
person(istream &in) {read(in,*this);}
private: //以下成员只可以被类的成员函数访问
string name;
string addr;
person& copy(const person &x)
{
name=x.name;
return *this;
}
}

需要注意,private说明符后的成员只可以被类的成员函数访问,像前面讲到的read和print函数不属于类的成员函数,因此无法访问private中的成员。

友元

若想非类成员函数访问private内容,可以在类中使用friend关键字对其进行友元声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class person
{
friend istream &read(istream &in, person &item);
friend ostream &print(ostream &out, person &item);
}

istream &read(istream &in, person &item)
{
in>>item.name>>item.addr;
return in;
}
ostream &print(ostream &out, person &item)
{
out<<"NAME: "<<item.name<<endl;
out<<"ADDR: "<<item.addr<<endl;
return out;
}

友元声明也适用于声明其他类及其成员函数

1
2
3
4
5
6
7
8
9
class person
{
friend class test;
friend void test::test1();
}
class test
{
void test1(){...};
}

关于类的静态成员

在仔细阅读了书籍过后个人觉得用处不是特别大,如果以后用到了相关知识再在这里补充,目前就不写了。