}
intmain()
{
Studentstu("Wang",85);
output_student(stu);
}
设计了一个类 Student,数据成员有 name 和 score,有两个构造函数,有一个设置成员数据函数 set_student(),各有一个取得 name 和 score 的函数 get_name() 和 get_score()。
请注意 get_name() 和 get_score() 后面都加了 const,而 set_student() 后面没有(也不能有const)。
首先说一点题外话,为什么 get_name() 前面也加 const。
如果没有前后两个 const 的话,get_name() 返回的是对私有数据成员 name 的引用,所以通过这个引用可以改变私有成员 name 的值,如
Student stu("Wang", 85);
stu.get_name() = "Li";
即把 name 由原来的 "Wang" 变成了 "Li",而这不是我们希望的发生的。
所以在 get_name() 前面加 const 避免这种情况的发生。
那么,get_name() 和 get_score() 这两个后面应该加 const的成员函数,如果没有 const 修饰的话可不可以呢?
回答是可以!
但是这样做的代价是:
const对象将不能再调用这两个非const成员函数了。
如
const string& get_name(); // 这两个函数都应该设成 const 型
int get_score();
void output_student( const Student& student )
{
cout << student.get_name() << "\t"; // 如果 get_name() 和 get_score() 是非const成员函数,这cout << student.get_score() << endl; 一句和下一句调用是错误的
}
由于参数student表示的是一个对const Student型对象的引用,所以 student 不能调用非const成员函数如 set_student()。
如果 get_name() 和 get_score() 成员函数也变成非const型,那么上面的 student.get_name() 和 student.get_score() 的使用就是非法的,这样就会给我们处理问题造成困难。
因此,我们没有理由反对使用const,该加const时就应该加上const,这样使成员函数除了非const的对象之外,const对象也能够调用它。
Øconst用于函数返回引用类型:
函数返回值为引用常量表示不能将函数调用表达式作为左值使用。
例如返回引用的函数:
int&min(int&i,int&j);
可以对函数调用进行赋值,因为它返回的是左值:
min(a,b)=4;
但是,如果对函数的返回值限定const:
constint&min(int&i,int&j);那么,就不能对min(a,b)调用进行赋值了。
引用变量
引用是已定义变量的别名(另一个名称),引用变量的主要用途是用作函数的形参。
通过将引用变量用作参数,函数将使用原始数据,而不是其副本。
Ø创建引用变量:
inta;
int&b=a;//b是引用变量,是变量a的引用
&不是地址运算符,而是类型标识符的一部分(此处为int&),必须在声明引用变量时进行初始化,而不能向指针那样可以先声明,再赋值。
Ø引用更接近const指针,必须在创建时进行初始化,一旦与某个变量关联起来,将一直效忠于它。
inta;
int&b=a;=>int*constp=&a;
引用变量b扮演的角色与表达式*p相同。
Ø当将引用作为函数参数时,调用函数时该参数就成为实参的别名,这种传递参数的方法称为按引用传递。
按引用传递允许被调用函数能够访问调用函数中的变量。
按值传递导致被调用函数使用调用程序的值的拷贝(副本)。
Ø如果程序员的意图是让函数使用传递给它的信息,而不对这些信息进行修改,同时又想使用引用,则应使用常量引用。
函数原型如:
doublecube(constdouble&a);
Ø如果实参与引用参数不匹配,C++将生成临时变量,当然前提是形参为const引用,C++才允许这么做。
简而言之,如果引用参数是const,则编译器将在两种情况下生成临时变量:
实参类型正确,但不是左值;实参类型不正确,但可以转换为正确的类型。
Ø将引用形参声明为const的好处:
1)使用const可以避免无意中修改数据的编程错误2)使用const使函数能够处理const和非const实参,否则将只能接受非const数据;3)使用const引用使函数能够正确生成并使用临时变量。
Ø引用不仅可以用于基本变量,也可以用于结构和类
类
类和对象
Ø类是一种抽象的数据类型,对象是类的实例。
类的设计
Ø第一步提供类声明:
类声明类似结构声明,可以包含数据成员和函数成员;声明有私有部分,在其中声明的成员只能通过成员函数进行访问;声明还有公有部分,在其中声明的成员可被使用类对象的程序直接访问;通常数据成员被放在私有部分中,成员函数被放在公有部分中,因此典型的类声明的格式如下:
classclassName
{
private:
datamemberdeclarations
public:
memberfunctionprototypes
}
公有部分的内容构成了设计的抽象部分—公有接口;将数据封装到私有部分中可以保护数据的完整性,这被称为数据隐藏。
Ø第二步是实现类成员函数:
可以在类声明中提供完整的函数定义,而不是函数原型;但是一般单独提供函数定义,这种情况下,需要使用作用域解析运算符来指出成员函数属于哪个类。
类成员访问控制
Ø类通过使用关键字来控制对类成员的访问。
Ø三个关键字:
private、public、protected,其中private关键字可以省略,因为类的默认访问类型为private。
Ø使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数或友员函数来访问对象的私有成员。
公有成员函数是程序和对象的私有成员之间的桥梁,提供了对象和程序之间的接口。
防止程序直接访问数据称为数据隐藏。
Ø基类成员对其对象的可见性:
公有成员可见,其他不可见,这里保护成员同于私有成员。
Ø基类成员对派生类的可见性:
公有成员和保护成员是可见的,而私有成员是不可见的。
Ø基类成员对派生类对象的可见性:
所有成员都是不可见的。
静态类数据成员
Ø静态成员有一个特点,无论创建了多少对象,程序都只创建一个静态类变量副本,类的所有对象共享同一个静态成员。
Ø对于静态类数据成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。
但是当静态数据成员为const整数类型或枚举类型,则可以在类声明中初始化。
友元
C++引入了友元实现了在类的外部访问类的私有成员的功能。
这样,即不放弃私有数据的安全性,又可在类的外部访问类的私有成员。
但一定程度上说友元破坏了类的封装性,在使用友元时一定要慎重。
友元关系是单向的,也是不能传递的。
Ø友元函数
一个普通函数作为某个类的友元时即为友元函数。
在该函数中可以访问其由friend声明语句所在的类的对象的私有成员和公有成员。
在类中作如下声明,则说明该函数不是本类的成员函数,而是友元函数。
friend函数类型友元函数名(参数表);
友元函数的定义可以在类内也可以在类外,在类外定义时不需要加类名和普通函数定义没有区别。
通常友元函数的定义在类外进行。
友元函数不是类的成员,因而不能直接引用对象成员的名字,也不能通过this指针引用对象的成员,必须通过作为入口参数传递进来的对象名或对象指针来引用该对象的成员。
为此,友元函数一般都带有一个该类的入口参数。
Ø友元成员函数
某个类的成员函数作为另一个类的友元即为友元成员函数。
通过友元成员函数,可以访问由friend声明语句所在的类的对象的私有成员和公有成员。
当一个类A的成员函数作为另一个类B的友元函数时,在类B中的声明格式为:
friend函数类型成员函数所在类类名:
:
函数名(参数表);
Ø友类
当一个类作为另一个类的友元时即为友类。
若类A是类B的友类,则类A中的所有成员函数都是类B的友元成员函数,所以可以通过对象名访问B的私有成员和公有成员。
当类A为类B的友类时,在类B中的声明格式为:
friendclass<友元类名>;或friend<友元类名>;
有关返回对象的说明
当成员函数或独立的函数返回对象时,有几种返回方式可供选择。
可以返回指向对象const对象的引用、返回指向非const对象的引用、返回const对象
Ø返回指向const对象的引用
Ø返回指向非const对象的引用
Ø返回对象
Ø返回const对象
特殊成员函数
C++自动提供下面这些成员函数
✓默认构造函数,如果没有定义构造函数
✓默认析构函数,如果没有定义
✓复制构造函数,如果没有定义
✓赋值运算符,如果没有定义
✓地址运算符,如果没有定义
✓C++11提供了另外两个特殊成员函数:
移动构造函数和移动赋值运算符
类对象与赋值运算符
C++允许类对象赋值,这是通过自动为类重载赋值运算符实现的。
这种运算符的原型如下:
Class_name&Class_name:
:
operator=(constClass_name&);
Ø赋值运算符的功能以及何时使用它
将已有的对象赋给另一个对象时,将使用重载的赋值运算符:
StringBadheadline1(“C++”);
…
StringBadknot;
Knot=headline1;
特殊情形,当初始化对象时,有可能使用赋值运算符:
StringBadmetoo=knot;
Metoo是一个新创建的对象,被初始化为knot的值,因此使用复制构造函数,当实现时是分两步来处理时将使用赋值运算符:
使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值复制到新对象中。
Ø赋值运算符的问题以及如何解决
默认赋值运算符的浅复制可能会产生一些错误,解决办法就是提供赋值运算符(进行深度复制)定义。
下面代码说明如何为StringBad类编写赋值运算符:
StringBad&StringBad:
:
operator=(constStringBad&st)
{
if(this==&st)
return*this;
delete[]str;
len=st.len;
str=newchar[len+1];
std:
:
strcpy(str,st.str);
return*this;
}
●由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]来释放这些数据
●函数应当避免将对象赋给自身;否则,给对象重新赋值前,释放内存操作可能删除对象的内容。
●函数返回一个指向调用对象的引用。
复制构造函数
复制构造函数用于将一个对象复制到新创建的对象中。
也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的的赋值过程中。
类的复制构造函数的原型通常如下:
Class_name(constClass_name&);
它接受一个指向const对象的引用作为参数。
Ø新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。
假设motto是一个StringBad对象,则下列4中声明都将调用复制构造函数:
StringBadditto(motto);
StringBadmetoo=motto;
StringBadalso=StringBad(motoo);
StringBad*pStringBad=newStringBad(motto);
中间两种声明可能会使用复制构造函数直接创建metoo和also,也可能使用复制构造函数生成一个临时对象,然后将临时对象的内容赋给metoo和also,取决于具体的实现。
Ø每当程序生成对象副本时,编译器都将使用复制构造函数。
具体地说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。
Ø默认复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。
可能会存在问题,解决办法是定义一个显式复制构造函数。
类的构造函数和析构函数
构造函数:
Ø类的数据部分的访问状态是私有的,这意味着程序不能直接访问数据成员,所以类不能像标准类型那样对变量进行初始化。
我们知道程序只能通过成员函数来访问数据成员,为此,C++提供一个特殊的成员函数—类构造函数,专门用于构造新对象、将值赋给它们的数据成员。
构造函数名与类名相同,此外构造函数的原型和函数头有一个有趣的特征--虽然没有返回值,但没有被声明为void类型。
Ø构造函数分为默认构造函数和非默认构造函数,均存在显式和隐式调用两种方式。
当且仅当函数没有定义任何构造函数时,编译器才会提供默认构造函数。
否则必须要定义默认构造函数。
Ø定义默认构造函数的方式有两种:
一种是给已有构造函数所有参数提供默认值;另一种是没有参数的构造函数。
Ø编译器提供的默认构造函数,不执行任何操作。
Ø显式调用和隐式调用,例:
classStock
{
Private:
….
Public:
…
}
Stockfirst=Stock();//显式调用默认构造函数
Stockfirst;//隐式调用默认构造函数
Stockfirst(”C++”,20,15);//隐式调用非默认构造函数
Stockfirst=Stock(”C++”,20