欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C++常见问题总结:类的特性讲解

程序员文章站 2022-07-01 18:24:50
类的一些特性 定义一个类型成员 除了定义数据和数据成员之外,类还可以自定义某种类型在类中的别名。由类定义的类型名字和其他成员一样存在访问限制。 class screen{ public:...

类的一些特性

定义一个类型成员

除了定义数据和数据成员之外,类还可以自定义某种类型在类中的别名。由类定义的类型名字和其他成员一样存在访问限制。

class screen{
public:
 //using pos=string::size_type;
 typedef string::size_type pos;
private:
 pos cursor=0;
 pos height=0,width=0;
 string contents;
};

用来定义类型的成员必须先定义后使用,这一点与普通成员有区别。

成员函数

class screen{
public:
 //using pos=string::size_type;
 typedef string::size_type pos;
 screen()=default;
 screen(pos ht,pos wd,char c):height(ht),width(wd),contents(ht*wd,c){}
 char get() const  //隐式inline
 {return contents[cursor];}
 inline char get(pos ht,pos wd) const;//显示inline函数
 screen &move(pos r,pos c);//能在定义时设为inline
private:
 pos cursor=0;
 pos height=0,width=0;
 string contents;
};

令成员成为inline函数

内联函数:一次函数调用其实包含着一系列的工作,调用前要先保存寄存器,并在返回时恢复;可能需要拷贝实参;程序转向一个新的位置继续执行。将函数指定为内联函数,通常就是将他在每个调用点上“内联”的展开。内联说明只是向编

译器发出的一种请求,编译器可以选择忽略这个请求。inline成员函数与inline函数一样应该与相应的类定义在同一个头文件中。

成员函数的声明必须在类的内部,它的定义即可在内部定义也可以定义在类的外部,定义在内部为隐式内联的。inline关键字可以在声明处或定义处出现也可以同时出现。

//外部定义处为inline
inline screen&screen::move(pos r,pos c)
{
 pos row=r*width;
 cursor=row+c;
 return *this;
}
//内部声明处指定为inline
char screen::get(pos r,pos c) const
{
 pos row=r*width;
 return contents[row+c];
}

this指针

成员函数通过一个名为this的额外隐式参数来访问调用它的那个对象。当我们调用一个成员函数时,用请求该函数的对象的初始地址初始化this。

myscreen.get();
//伪代码
screen::get(&myscreen);

在成员函数内部,我们可以直接使用该函数的对象的成员,它隐式的使用this指向的成员。

对于我们来说,this 形参是隐士定义的。 实际上, 任何定义名为 this 的参数变量的行为都是非法的。 我们可以再成员函数内部使用 this(没有必要)如:

char get() const{return this->contents;}

this的目的总是指向这个对象,所以是一个常量指针,不能改变this中的地址。

返回*this的成员函数

我们继续为screen添加一些函数。

class screen{
public:
 screen &set(char);
 screen &set(pos,pos,char);
 //其他和之前版本一样
};
inline screen &screen::set(char c)
{
 contents[cursor]=c;
 return *this;
}
inline screen &screen::set(pos r,pos col,char ch)
{
 contents[r*width+col]=ch;
 return *this;
}

我们无须使用隐式的this指针访问函数调用者的某个具体成员,需要把调用函数的对象当成一个整体来使用。

如果一个const成员函数以引用的形式返回*this,那么它返回的类型将是常量引用。

const成员函数

默认情况下,this 的类型是指向类类型非常量版本的常量指针。尽管 this 是隐士的,但它仍然要遵循初始化规则,我们不能把 this 绑定到一个常量对象上,这一情况使我们不能再一个常量对象上调用普通的成员函数。因此,声明成常量成员函数(const 关键字放在成员函数的参数列表之后。)

修改隐式 this 指针的类型,让他为指向 const。常量成员函数不能改变调用他的对象的内容,常量对象、常量对象的指针和引用只能调用常量成员函数。

常量对象——>const 成员函数

非常量对象——>const 成员函数/非 const 成员函数(非常量版本是一个更好的匹配)

基于const的重载

根据成员函数是否是const的,我们可以对其进行重载。

class screen{
public:
 //根据对象是否是const重载了display
 screen &display(ostream&os)
 {do_display(os);return *this;}
 const screen &display(ostream&os) const
 {do_display(os);return *this;}
private:
 void do_display(ostream &os) const
 {os<

可变数据成员 一个可变数据成员永远不会是const,即使它是const对象的成员。因此,一个const成员函数可以改变一个可变成员的值。

class screen{
public:
 void some_member() const;
private:
 mutable size_t access_ctr;
};
void screen::some_member() const
{
 ++access_ctr;
}

友元

类可以允许其他的类或者函数访问他的非公有成员,方法是令其他的类或者函数成为他的友元。友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。友元不是类的成员也不受它所在区域访问控制级别的约束。

友元的声明

友元的声明仅仅指定了访问权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们必须在友元声明之外在专门对函数进行一次声明。

令类成为友元

class screen{
 //window_mgr的成员可以访问screen的私有部分
 friend class window_mgr;
 //剩余部分
};

class window_mgr{
public:
 using screenindex=vector::size_type;
 void clear(screenindex i);
private:
 vector screens{screen(24,80,' ')};
};
void window_mgr::clear(screenindex i)
{
 screen &s=screens[i];
 s.contents=string(s.height*s.width,' ');
}

友元关系不存在传递性,每个类负责控制自己的友元类或友元函数。

令成员函数成为友元

class screen{
 //必须指出成员函数属于哪个类
 friend void window_mgr::clear(screenindex i);
};

当我们令某个成员函数作为友元,我们必须仔细组织程序的结构以满足声明和定义的彼此依赖关系。在此例中:

1、首先定义window_mgr类,其中声明clear函数,但是不能定义它。

2、接下来定义screen,包括对clear的友元声明。

3、最后定义clear,此时它才可以使用screen的成员。

友元声明与作用域

struct x{
 friend void f() {//友元函数可以定义在类的内部}
 x() {f();}  //错误:f还没被声明
 void g();
 void f();
};
void x::g() {return f();} //错误 f 没有声明
void f();
void x::h(){return f();}//正确: 现在 f 的声明在作用域中

当一个名字第一次出现在有个友元声明中时, 我们隐式的假定该名字在当前作用域中是可见的。然而,友元本身不一定真的声明在当前作用域中。甚至就算在类的内部定义该函数,我们也必须在类的外部提供相应的声明从而使得该函数可见。

类的作用域

每个类都会定义它自己的作用域。在类的作用域之外,普通的数据和函数成员只能由对象、引用或者指针使用成员访问运算符来访问。对于类类型(别名)成员则使用作用域运算符访问。

screen::pos ht=24;
screen scr(ht,wd,’ ’);
screen *p=&scr;
char c=scr.get();
c=p->get();

作用域和定义在类外部的成员

一个类就是一个作用域,在类的外部,成员的名字被隐藏起来了。一旦遇到了类名,定义的剩余部分就在类的作用域之内了,这里的剩余部分包括参数列表和函数体。此外函数的返回类型通常出现在函数名之前。因此当成员函数定义在类的外部时,返回类型中使用的名字都位于类的作用域之外。

名字查找与类的作用域

1、首先,在名字所在的块中寻找其声明语句,只考虑在名字的使用之前出现的声明。

2、如果没找到,继续外层作用域查找。

3、如果最终没找到,则程序报错

对于定义在类内部的成员函数来说(两阶段):

1、首先编译成员的声明。

2、直到类全部可见后才编译函数体。

用于类成员声明的名字查找

这种两阶段的处理方式只适用于成员函数中使用的名字。声明中使用的名字,包括返回类型或者参数列表中使用的名字,都必须在使用前确保可见。如果某个成员的声明使用了类中尚未出现的名字,则编译器会在定义该类的作用域中继续查找。

typedef double money;
string bal;
class account{
public:
money balance(){return bal;}
private:
money bal;
}

成员函数返回的是bal成员,而不是外层作用域中的string bal。

类型名要特殊处理

一般来说,内层作用域可以重新定义外层作用域中的名字,即使该名字已经在外层作用域中使用过。然而在类中,如果成员使用了外层作用域中的名字,而该名字代表一种类型,则类不能再之后重新定义该名字:

typedef double money;
string bal;
class account{
public:
money balance(){return bal;} //使用外层作用域中的 money
private:
typedef double money; //不能重现定义 错误(一般编译器会忽略此错误)
money bal;
};

类型名的定义通常出现在类开始处,这样就能确保所有使用该类型的成员都出现在类名的定义之后。

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

1、首先,在成员函数内查找该名字的声明。和前面一样,只有在函数使用之前出现的声明才被考虑。

2、如果在成员函数内部没有找到,则在类内继续查找,这时类的所有成员都可以被考虑。

3、如果在类内没有找到该名字的声明,在成员函数定义之前的作用域内继续查找。

当成员定义在类的外部时,名字查找的第三步不仅要考虑类定义之前的全局作用域中的声明,还需要考虑在成员函数定义之前的全局作用域中的声明。

class screen{
public:
 typedef string::size_type pos;
 void dummy_fcn(pos height)
 {
  //隐藏了同名的成员。
  //可以使用作用域运算符显示的访问
  //cursor=width*screen::height;
  cursor=width*height;
 }
private:
 pos cursor=0;
 pos height=0,width=0;
};

类类型

每个类定义了唯一的类型。对于两个类来说,即使他们的成员完全一样,这两个类也是不同的类型。

类的声明

class screen;//类的声明

可以声明一种类而不定义他,在声明之后定义之前属于一种不完全类。此时我们已经知道他是一个类类型,但是不清楚它到底包含哪些成员。

不完全类型只能在非常有限的情况下使用:

1、 可以定义指向这种类型的指针或者引用。

2、 可以声明(但是不能定义)以不完全类作为参数或者返回类型的函数。只有类被定义之后数据成员才能被声明成这种类型。换句话说,我们

必须首先完成类的定义,然后编译器才能知道存储该数据成员需要多少空间。因为只有当类全部完成后类才算被定义,所以一个类的成员类型不能是该类自己,然而一旦一个类的名字出现后,就认为是被声明了,因此类允许包含指向他自身的类型的引用或指针。

class link_screen{
 screen window;
 link_screen*next;
 link_screen*prev;
}

聚合类

聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。

满足条件:

1、所有成员都是 public 的。

2、没有定义任何构造函数。

3、没有类内初始值

4、没有基类,也没有 virtual 类。

struct data{
int ival;
string s;
};

初始化方式:

data vall={0.”anna”};

初始值的顺序必须与声明的顺序一致,与初始化数组元素的规则一样,如果初始值列表中的元素个数少于类的成员的数量,则靠后的成员被值初始化。初始值列表的元素的个数不能超过类的成员的数量。

;}>