c++类

定义一个类

struct Sales_data{

  std::string isbn()const ;

  Sales_data& combine(const Sales_data&);

  std::string bookNo;

  unsigned units_sold=0;

  double revenue=0.0;

};

1、成员函数

成员函数的声明必须在类的内部,它的定义既可以在类的内部,也可以在外部,定义在内部的函数是隐式的inline函数,在外部定义需要使用作用域运算符::。

1.1、构造函数

构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数

  • 构造函数名与类名相同
  • 没有返回类型
  • 可以重载
  • 不能声明成const

(1)合成的默认构造函数

如果我们没有为类显式的定义构造函数,编译器会为我们隐式的定义一个默认构造函数(不需要任何参数)又叫做合成的默认构造函数)这个构造函数按照下面规则初始化类的数据成员:

  1. 如果有类内初始值,用它来初始化成员(比如上面的units_sold=0,revenue=0.0)。类内初始值必须以=或{}表示。
  2. 否则,默认初始化(如bookNo被初始化为空字符串)。

(2)自定义默认构造函数

 一般情况下,类都需要定义一个默认构造函数,主要有以下三个原因:

  1. 一旦定义了其他构造函数,除非我们自己再定义一个默认构造函数,否则这个类将没有默认构造函数。
  2. 若类中含有内置类型或复合类型(比如数组和指针)的数据,如果没有提供类内初始值(有的编译器可能不支持类内初始值),使用合成的默认构造函数的值将是未定义的。
  3. 如果类中包含其他类的对象,而这个对象没有默认构造函数,那么编译器不能为类合成默认构造函数,我们必须自己定义一个默认构造函数。

Sales_data()=default;

函数名与类名相同,没有参数列表,参数列表后面加上=default要求编译器生成默认构造函数。

(3)构造函数初始值列表

Sales_data(const string & s,unsigned n,double p):bookNo(s),units_sold(n),revenue(p*n){}

冒号和花括号中间的部分就是构造函数的初始值列表。如果某个数据成员没有被初始值列表包括,这个值将以与合成默认构造函数相同的方式隐式初始化,对于没有初始值列表的构造函数,也是以同样的方式初始化

1.2、this指针

成员函数通过一个名为this的隐式参数来调用它的那个对象,当我们调用一个成员函数时,用请求该函数的对象的地址来初始化this指针。因此任何自定义名为this的参数或变量的行为都是违法的,我们也可以在函数内部使用this。

string fun(){return this->no;}

this是一个常量指针,不允许改变this中保存的地址。

(1)、返回this对象

 在函数中使用*this来解引用得到执行该函数的对象:

return *this;

1.3、const 成员函数

string fun() const{};

在参数列表后的const关键字的作用是修改隐式this指针的类型,上面的例子中,this的类型是Sales_data * const,意为着我们不能把this绑定到一个常量对象上,因此,我们不能在一个常量对象上调用成员函数isbn()。如果使用了const成员函数,this就会变成const Sales_data * const类型了,this可以指向常量对象,isbn函数体内不能改变this所指的对象。而combine函数不需要const,因为常量对象不能使用这个函数,这个函数的对象会改变自身的内容。

在类的外部定义const成员函数的话也必须在参数列表后明确指定const属性,同时指定它所属的类名:

std::string Sales_data::isbin()const{

  return bookNo;

}

通过区分成员函数是否是const的,可以对其进行重载。

1.4、类的静态成员

类的静态成员存在于任何对象之外,只与类有关,而与具体的对象无关,用static关键字表示。包括静态数据成员和静态成员函数。对象中不包含任何与静态数据成员有关的数据,静态成员函数也不与任何对象绑定在一起,它们不包含this指针。

(1)静态数据成员

(1.1)定义并初始化静态数据成员

因为静态成员不属于类的任何一个对象,因此它并不是在对象创建的时候定义的,因此也不是由类的构造函数初始化的。一般来说,我们不能在类的内部初始化静态成员(可以为静态成员提供const整数类型的类内初始值,要求静态成员必须时constexpr类型的),必须在外部定义和初始化每个静态成员,在外部定义的时候不能重复static关键字

static int num;//声明

int C1::num=0;//C1类的静态成员num定义并初始化为0

(1.2)使用静态数据成员

成员函数可以直接使用静态成员。

(1.3)静态数据成员与普通成员的区别

  • 静态成员的类型可以是它所属类的类型,而非静态数据成员只能声明它所属类的指针或引用。
  • 可以使用静态成员作为默认实参,而普通成员则不行。

(2)静态成员函数

(2.1)定义静态成员函数

静态成员函数既可以在类的内部也可以在外部定义,在外部定义时,不能重复static关键字,该关键字只能出现在类内部的声明语句中

static void fun();//在内部声明

void C1::fun(){//在外部定义

}

(2.2)使用静态成员函数

静态成员函数不能声明成const的,也不能在函数体内使用this指针。可以使用类的对象、引用、或指针来访问:

C1 ac1;

C1 *ac2=&ac1;

ac1.fun();

ac2->fun();

2、类的拷贝、赋值和析构

如果我们不主动定义这些操作,编译器会自动为我们合成这些操作,对类的每个成员执行拷贝、赋值和析构操作。但是,当类需要分配类对象之外的资源时,合成版本常常会失效,比如管理动态内存的类。

3、访问控制与封装

(1)在C++中使用访问说明符加强类的封装性:

public:说明符之后的成员在整个程序内可被访问。

private:之后的成员可以被类的成员函数访问,但不能被使用该类的代码访问,private封装了类的实现细节

封装的2个优点:

  • 确保用户不会无意间破坏代对象的数据。
  • 在改变类的实现细节时不需要改变用户级别的代码。

(2)友元

类可以允许其他类或函数访问它的非共有成员,方法是使其称为它的友元,要想使一个函数成为它的友元,只要在类中增加一个以friend开头的声明语句即可:

friend Sales_data& add(const Sales_data&,const Sales_data&);

同时类外部的声明语句也不可或少:

Sales_data& add(const Sales_data&,const Sales_data&);

友元不是类的成员,也不受访问说明符的限制,为了使友元对类的用户可见,通常把友元的声明和类放在同一个头文件中。友元声明的作用只是影响访问权限,本身并非普通意义上的声明。

(3)struct和class的区别

唯一的区别是默认的访问权限不同,struct在第一个访问说明符之前的成员全是public的,而class在第一个访问说明符前的成员都是private的。

 4、类的声明

我们可以先声明一个类,而不定义它

class CC;

对于CC来说,在它声明之后定义之前是一个不完全类型可以定义指向这种类型的指针或引用,也可以声明这种类型作为参数或返回类型的函数。对于一个类来说,我们在创建它的对象之前,这个类必须被定义过,否则我们不知道申请多大的内存空间来存储它。一个类只有全部完成后才算定义完成,所以一个类的成员类型不能是自己,但可以是自己类型的引用或指针。但是引用不是要初始化的吗??

5、名字查找与类的作用域

3.1、类的作用域

类本身就是一个作用域,类的成员函数的定义也是嵌套在类的作用域之中的,所以,即使成员函数在外部定义也可以使用类的成员变量。定义在类外部的函数一旦遇到了类名,定义的剩余部分(参数列表和函数体)就在类的作用域之内了;而函数的返回类型出现先在函数名之前,它的作用域不在类中,此时返回类型如果是某个类的成员,必须指明是哪个类的成员。而类内部的成员是不需要使用类名作用域的。如:

class C1{  

public:
  typedef int Type1;//不能设置成私有的,否则在外部定义的成员函数的返回类型不能使用Type1,因为返回类型不在类的作用域之内
  Type1 fun1();  

};

C1::Type1 fun1(){}

3.2、名字查找

(1)一般的名字查找(寻找与所用名字最匹配的声明的过程)的过程:

  • 首先,在名字所在的块中寻找其声明语句,只考虑在名字使用之前出现的声明。
  • 如果没找到,继续查找外层作用域。
  • 如果最终没找到匹配的声明,则程序报错。

(2)类的成员名字查找

(2.1)类的定义的处理方式:

  • 首先,编译成员的声明(就算成员函数在类内部定义也只先编译声明部分)。
  • 知道类全部可见后才编译函数体。

编译器分两步处理类:首先编译成员函数的声明,然后才轮到成员函数体。因此,成员函数体可以随意使用类中的其他成员而无须在意这些成员出现的顺序声明中使用的名字(包括返回类型和参数列表中使用的名字)都必须在使用前确保可见。

sypedef double Money;

string bal;

class Account{

public:

  Money balance(){return bal;}

private:

  Money bal;

}

当编译器看到balance函数的声明语句时,它将在Account类的范围内寻找对Money的声明,编译器只考虑Account类中在使用Money前的声明,因为没找到匹配的成员,编译器会到外城作用域中查找,找到了typedef定义的语句。函数的形参为空,就不查找了。而函数体实在整个类可见后才被处理,bal返回类中的成员,而非外层 的string对象。

(2.2)类的成员定义中的名字查找过程:

  •  首先,在成员函数内查找该名字的声明,只有在函数使用之前出现的声明才被考虑。
  • 如果函数内没有找到,就在类中继续查找,这时,类的所有成员都可以被考虑。
  • 如果类内也没找到该名字的声明,在成员函数定义之前的全局作用域内继续查找。

(3)强制访问

如果成员变量被函数体内的名字隐藏,可以使用下面的方式来强制访问类的成员变量:

  this->成员变量

  类名::成员变量

访问全局变量名字使用

  ::变量名

 

posted @ 2018-02-16 11:15  StormWendy  阅读(195)  评论(0编辑  收藏  举报