类详解(一)成员变量,成员函数,友元及其他注意事项和概念

类详解(一)成员变量,成员函数,友元及其他注意事项和概念

创建类

创建类的方式有两种:一种是使用class关键字,另外一种是使用struct来创建类

这两种方式的区别是:在不加上访问说明符的基础上,class默认的成员都是私有的,struct默认的是公开的,所以一般如果所有的类成员和成员函数都是公开的可以直接使用struct。

class 类名

{

// 成员函数或者成员变量

public:

// 所有的成员变量或者成员函数都是公开的

private:

// 所有的成员变量和成员函数都是私有的

protected:

// 所有的成员变量和成员函数都是私有的

};

当然,也可以把class改成struct,效果是一样的。

在这段代码中,有3个访问说明符,其中private和protected两个都是私有的,唯一的不同是在后面面向对象继承上的不同(之后的文章讲解),而public则是公开的成员。

私有的类型不能类外访问,但是可以在类内访问,在有特别需求,想让其他的类或者外部函数访问类中的私有属性,可以使用友元进行声明(后面会讲到)。

不完全类型

class person;

这种就是不完全的类型,他虽然已经声明了他的类类型,但是在定义之前他是一个不完全类型,也就是说在定义之前并不知道他含有什么成员

this指针

在调用类中的成员变量或者成员函数的时候,有一个隐式指针指向这个类中的成员变量和函数。

类是我们自定义的类型,所以也可以让函数返回我们的自定义类型。

struct person

{

int age, hight;

string name;

person attribute(int age,int hight,string name)

{

this->age = age;

this->hight = hight;

this->name = name;

return *this;

}

};

int main(int argc,char **argv)

{

person p;

p.attribute(10, 160, "张三"); // 调用perosn中的成员函数

cout << "name:" << p.name << "\n"

<< "age:"<< p.age << "\n"

<< "hight:" << p.hight << endl;

}

从代码可以看到,perosn中的成员函数传入的参数与person中的成员变量同名,这种情况下编译器不能够分辨出哪一个是类的,哪一个是类的,如果我们不使用this指针的话,编译器默认在这个函数下的age,hight和name都是参数,所以加上this表示的是这个变量是类种的成员变量。返回的时候返回的是这个类类型,所以this指向的总是这个类对象。

需要注意的是:因为this指针总是指向类对象,所以this是一个常量指针,所以不能将this绑定到常量对象上,如果绑定到常量对象上的话,两者都不可变就不能在指向其他的成员

当然,返回这个二类的类型后依然可以使用点的形式来调用返回的类的成员属性,这也叫链式编程

类编译

类的编译分为两步:首先编译的是成员的声明和成员变量,之后在编译成员函数体

这样就意味着,在类中的成员变量的位置随意,可以在成员函数之前,也可以在成员函数之后,这个成员函数都能够调用这个成员变量。

类的作用域及外部成员函数的定义

类的作用域就像是std这样子的全局作用域一样,只需要在成员变量或者函数前面加上 leiming:: 成员名,就可以调用该类中的成员。

根据类的作用域,可以使得类中的成员函数在外部定义,但是前提是要先在类中声明。

struct person

{

int age, hight;

string name;

person attribute(int age,int hight,string name)

{

this->age = age;

this->hight = hight;

this->name = name;

return *this;

}

void shopping();

};

void person::shopping()

{

cout << "购物成员函数" << endl;

}

shopping是类的一个成员函数,在类中声明后,再类外使用作用域的形式来定义函数体。

const成员函数

在一些成员函数中,有一些函数的功能只是进行输出,并没有对类的属性进行修改,这样的成员函数我们可以将它们定义成常函数。在成员函数的形参列表后面加上const来表示这样的函数是常函数。

常函数需要注意的是:这个函数不能修改函数的属性

错误示范:

struct person

{

int age, hight;

string name;

void shopping()const;

};

void person::shopping() const

{

this.age = 10; // 错误,常函数下不能修改类属性

cout << "购物成员函数" << endl;

}

正确写法:

struct person

{

int age, hight;

string name;

void shopping()const;

};

void person::shopping() const

{

cout << "购物成员函数" << endl;

}

但是有的时候我们在很少的情况下不得不在长函数中修改属性的值,我们可以使用mutable修饰成员变量。

struct person

{

int age, hight;

mutable double money = 10; // 在常函数中可变

string name;

void shopping()const;

};

void person::shopping() const

{

--money;

cout << this->money<< endl;

}

友元

我们设置访问权限的时候,类外部的函数不能访问类中的私有属性,但是在一些特殊情况外部的函数需要访问到类中的私有属性,那么这个时候就可以在类中设置友元。

友元是在类中声明外部的函数,并且在这个函数声明前加上friend。

class person

{

friend void friend_func(person p); // 表示友元,可以访问私有属性

public:

int hight;

string name, sex;

void init(int age,int hight,string name,string sex)

{

this->age = age;

this->hight = hight;

this->name = name;

this->sex = sex;

}

private:

int age;

};

void friend_func(person p) // 访问类中的age属性

{

cout << p.age << endl;

}

令成员函数做内联函数

在类中的成员函数都是自动的内联函数,因此我们也可以将这个内联函数显示化

(关于内联函数的讲解 传送门)

class person

{

friend void friend_func(person p); // 表示友元,可以访问私有属性

public:

int hight;

string name, sex;

inline // 内联函数

void print()

{

cout << name << endl;

}

};

成员函数重载

成员函数的重载和非成员函数的重载是一样的

(关于非成员函数的重载 传送门)

需要理解的是,const函数发生重载的时候,如果传入的是一个非常量指针,那么this指针将这个实参转化成常量指针。

类中的名字查找和类作用域

对于普通的函数来说查找名字的方式

首先在所在的块中查找,只考虑名子的声明(即使外部的名字是函数,块内的是变量,该名字按照块内的形式)

如果没找到,再去寻找块外名字声明

如果没找到,程序报错

而类跟我们的函数不同

类成员声明的名字查找

在类中声明一个名字,系统会现在类的作用域中查找该名字,如果不存在这个名字则在类外查找,如果找不到则程序报错

typedef int type;

string name = "小明";

class person

{

public:

type hight;

string sex;

type print()

{

cout << name << endl;

}

private:

string name = "张三";

};

外部将int重命名,由于在类的内部找不到type的类型所以就在外部查找,而输出的name因为内部有name属性,所以就直接调用内部的属性

类型名的特殊处理

一般来说,内层作用域可以重新定义外层作用域中的名字,即使这个名字在类中使用过了,但是类不一样,如果成员在类中使用了外层的名字,并且这个名字代表的是一种类型,那么不能在类中重新定义这个名字。

typedef int type;

string name = "小明";

class person

{

public:

type hight;

string sex;

type print()

{

cout << name << endl;

}

private:

typedef string type; // 错误,不能重新定义type

string name = "张三";

};

尽管重定义类型名是错误的,但是很多的编译器并不会检查这个错误并且能够顺利的运行

类型名的定义通常写在类的开头,这样确保后面使用该类型名的函数的类型

成员定义普通作用域的名字查找

在成员函数中的名字的查找与普通函数类似,如果类中的成员变量的作用名和成员函数中的形参的作用名相同额时候,在该成员函数的调用中优先选择形参的类型,如果想在该函数中调用成员变量加上一个this指针指向这个变量即可。

聚合类

聚合类可以直接的访问类中的成员,并且具有特殊的初始化方式。

聚合类的条件

所有的成员都是pubic

没有定义任何的构造函数

没有类内初始化

没有基类,也没有虚函数

下面是一个聚合类

struct person

{

int val;

string name;

};

int main(int argc,char **argv)

{

person p{ 100,"张三" };

cout << p.name << ":" << p.val << endl;

}

需要注意的是,这个列表中的成员初始化的顺序是按照成员变量在类中的声明顺序进行赋值的

静态成员

有的时候一个成员函数是跟类保持相关联的,而不是跟类对象保持关联,他不属于任何一个类对象,是所有在该类中创建的对象共享的。这样的成员函数为静态成员函数。

由于静态成员函数不跟任何一个对象关联,所以不能使用this指针。

声明静态成员对象

声明静态成员对象使用static关键字。当然,静态成员对象可以在类中声明类外实现,但是需要注意的是,static关键字只能出现在类内,类外不需要使用static关键字。静态成员需要在类内声明,类外初始化

struct person

{

static int i;

static void num();

};

int person::i = 0; // 类外初始化

void person::num() // 全体类对象共享一份函数

{

i++;

}

int main(int argc,char **argv)

{

person p;

person p1;

p.num();

cout << person::i << endl;

p1.num();

cout << p1.i << endl;

}

在该代码中可以看到,p访问后,p1在访问,因为访问了2次,所以i= 2,并且里面还体现了访问静态成员函数的两种方法,一种是直接使用类名:: 作用域的方式访问静态成员,另一种是直接使用对象名.静态成员名 的形式访问。

静态成员使用的特殊场合

有些非静态成员不能实现的,静态成员可以实现,例如:静态成员可以使用不完全的类型,而非静态成员只能是类类型的指针或者引用

class person

{

static person age; // 正确

person *name; // 正确

person hight //错误,只能用指针或者引用

}

更多创意作品