公用数据的保护:
有些数据往往是共享的,此时误操作可能会改变数据的状况;可以使用const将有关的数据定为常量;
1. 常对象:
定义 : Time const t1(12,57,29); == const Time t1(12,57,29);
一般形式为: 类名 const 对象名(实参表列) 或 const 类名 对象名(实参表列)
注:
1. 常对象中的成员变量为常变量且必须有初值;
2. 此时在所有的场合之下,所有的数据成员的值都不可以改变,
3. 如果一个对象被定义为常对象,则不能调用该对象的非 const类型的成员函数(构造和析构除外)!!
例:
const Time t1(10,58,30);
t1.get_time(); // 错误,函数为非 const 类型的成员函数;
这是为了防止这些函数修改常对象中的成员的值;
所以当需要引用常对象中的数据成员时,需要将成员函数声明为 const ,如: void get_time() const;
这表示常成员函数。 常成员函数可以访问常成员对象中的数据成员,但是不可以修改(只读);
有时为了编程需要,一定要修改常对象中的某个成员的值,此时可以做特殊处理:
mutable int count;
即把 count 定义为可变的数据成员,此时可以用声明为 const 的成员函数来修改它;
2. 常对象成员:
可以在声明时把成员声明为 const 类型,包括常数据成员 和 常成员函数;
1. 常数据成员:
其作用和一般常变量相似,用 const 来声明常数据成员;
注:
只能通过构造函数的参数初始化表对常数据成员进行初始化!!!!
如:
class Time
{
const int hour;
};
Time :: Time(int h)
{
hour = h; // 此时是非法的,因为常数据成员是不可以被赋值的!!
}
Time :: Time(int h) : hour(h){} // 通过参数初始化表对常成员变量进行赋值;
当一个类声明了一个数据成员为 const 时, 该类的所有成员中的该数据成员的值都不可变(可以不同);
2. 常成员函数:
对于一般的成员函数,可以引用本类中的非 const 成员和修改他们;
但是对于 常成员函数,只能引用本类中的数据成员,但是不能修改他们;
例:
void get_time() const;
const 是函数类型的一部分,在声明和定义函数的时候都必须要有 const 关键字,在调用时可没有;
注:
常成员函数可以引用 const 数据成员(非const函数不可),也可以引用非 const 数据成员;
const 数据成员可以被 const成员函数引用,亦可以被非 const 的成员函数引用;
常成员函数的应用:
1. 如果在一个类中,有些数据成员的值需要改变,而另一些数据成员的值不允许变化:
可以将一部分成员声明为 const ,以保证其值不会被改变。
此时非 const 类型的函数可以引用这些数据成员的值,并修改非 const 数据成员的值;
2. 如果要求所有的数据成员的值都不可改变:
则可以将对象为 const ,然后再用 const 成员函数引用成员,起到双保险作用!;
3. 如果已经定义了一个常对象:
此时只能调用其中的 const 型的成员函数,而不能调用非const类型的成员函数(不论是否会修改值);
如果需要访问对象中的数据成员,可将常对象中的所有成员函数都定义为 const 类型;
注:
1. 常对象只保证数据成员的值不被修改,但是如果成员函数未加 const声明,编译系统会当做非const处理;
2. 常成员函数不能够调用另一个非 const 函数!;
指向对象的常指针:
将指向对象的指针变量声明为 const 即,指针始终保持初值,指向不改变;
如:
Time t1(12,53,30),t2;
Time *const pt = &t1; // 定义一个常指针,指向t1
pt = &t2; // 错误,常指针的指向不可改变;
定义指向对象的常指针的一般形式:
类名 * const 指针变量名 = 对象地址;
注: 对象的常指针的指向不可改变,但是可以改变原对象中的值!
往往用常指针来作为函数的形参,此时函数在执行过程中,指针变量的值始终不变;
此时如果认为改变该形参的值,则编译系统将会报错,避免错误的发生!
指向常对象的指针变量:
指向常变量的指针变量:
例: const char *ptr; // 指向常变量的指针变量;
注: *const ptr; // 常指针!
原式表示指针指向的是一个常变量,不能通过指针来改变它的值;
定义的一般形式为:
const 类型名 * 指针变量名;
说明:
1. 如果一个变量已经被声明为常变量:
只能用指向常变量的指针指向它,不能用非 const 类型的指针指向;
如:
const char c[] = "Boy";
const char *ptr;
ptr = c; // 注意,此处为数组名,已经是首地址!
char *p2 = c; // 非法,因为数组 c 为常变量!!
2. 指向常变量的指针除了可以指向常变量,还可以指向未被声明的 const 的变量(非 const):
此时不能通过指针来改变他们的值!
如:
char c1 = 'a';
const char *p;
p = &c1; // 定义一个 const 指针指向一个非 const 类型的变量;
*p = 'b'; // 错误,试图用 const 指针改变一个变量;
c1 = 'b'; // 正确,原变量为 非 const 类型;
3. 如果函数的形参是指向非 const 类型的指针:
实参只能使用指向非 const 类型的指针,而不能使用 const 类型的指针;
这样在执行程序的过程中可以改变形参指针所指向的变量的值!
4. 如果函数的形参是指向 const 类型的指针:
在函数的执行过程中,不可以改变指针变量所指向的变量的值,
因此允许实参是指向 const 或 非 const 类型的指针;
如:
const char str[] = "Boy";
void fun(char *ptr); // 函数声明为行参是 非 const 类型的指针;
fun(str); // 非法,实参是 const 类型;
即对于指向常变量的指针变量:
指向常变量的指针变量可以指向 const 和 非 const 类型的变量;
但是指向非 const 类型变量的指针变量只能指向 非 const 型变量;
指向常对象的指针变量:同理
1. 如果一个对象已经被声明为常对象:
只能用指向常对象的指针来指向它,而不能用一般的(非 const)指针变量来指向它;
2. 如果已经定义了一个指向常对象的指针变量:
并使它指向一个非 const 类型的对象,则其指向的对象的值不可以通过指针改变(原变量值可改变);
例:
Time t1(12,24,43);
const Time *p = &t1;
t1.hour = 19; // 正确;
p->hour = 19; // 错误,该指针为一个指向常变量的指针;
注:
Time *const p; // 指向对象的常指针变量,只能指向一个固定的对象,不能改变指向的对象;
const Time *p; // 指向常对象的指针变量,可以指向 非 const 此时不能改变值;
3. 指向常对象的指针最常用于函数的形参,目的在于保护形参指针所指向的对象(不会改变指向);
例:
int main()
{
void fun(const Time *p); // 形参是指向常对象的指针变量;
Time t1(12,24,12);
fun(&t1); // 实参是对象t1(非 const)的地址,合法;
return 0;
}
void fun(const Time *p)
{
p->hour = 18; // 错误,试图用指向常变量的指针改变值;
cout << p->hour <<endl;
}
对象的常引用:
对于一个变量的引用,其实就是一个别名!
如果形参为变量的别名,实参为变量名,则在调用函数时,虚实结合于同一个区域,
此时可以通过引用名直接操作;
class Time
{
public:
Time(int h,int m,int s) : hour(h),minute(m),sec(s) {}
int hour;
int minute;
int sec;
};
void fun(Time &t)
{
t.hour = 18;
}
int main()
{
Time t1(10,22,34); // 此时hour 为 10;
fun(t1); // 修改利用引用 把hour修改为 18;
cout << t1.hour << endl;
return 0;
}
对象的动态建立和释放:
有时人们希望在需要用到对象时才建立对象,在不需要对象时就撤销它,释放内存;(如链表)
对象也可以使用 new 和 delete 动态建立和撤销一个对象;
例:
new Box;
此时建立了一个内存空间来存储一个 Box 型变量,并调用其构造函数;
但此时用户不能够访问这个对象,因为对象既没有变量名也没有获得地址 ----无名对象;
所以需要定义一个指针变量:
Box *pt;
pt = new Box; 或 Box *pt = new Box(12,25,18);
此后在程序中便可以通过 pt 指针来访问该对象;
在不需要使用由new 产生的对象的时候,可以用delete 运算符予以释放;
注:
delete pt;
这就撤销了 pt 指向的对象,但是要注意指针变量的当前指向,以免删错了对象;
执行delete 后,自动执行析构函数,以完成善后工作!
对象的赋值和复制:
1. 对象的赋值:
如果对于一个类,定义了两个或者多个对象,可以用 = 互相赋值!
注:
这里的赋值指的是数据成员的赋值,是通过 = 运算符的重载实现的,将一个对象的成员值一一复制;
一般形式 : 对象名 1 = 对象名 2; (两个对象属于同一个类);
如:
class Box
{
public:
Box(int l = 10,int w = 10,int h = 10);
int volume();
private:
int length;
int width;
int height;
};
Box :: Box(int l,int w,int h)
{
length = l;
width = w;
height = h;
}
int Box :: volume()
{
return (length * width * height);
}
int main()
{
Box box1(15,20,30);
cout << "The volume of the box is :" << box1.volume() << endl;
Box box2 = box1;
cout << "The volume of the box is :" << box2.volume() << endl;
return 0;
}
说明:1. 对象的赋值只对其中的数据成员赋值,而不对成员函数赋值;
2. 类的数据成员中不能够包括动态分配的数据,否则在赋值时可能会出现严重错误!
2. 对象的复制:
用一个 “已有的 ” 对象快速的复制出多个相同的对象;
如:
Box box2(box1);
作用为 用一个已有的对象 box1 克隆出另一个对象 box2;
一般形式为: 类名 对象名1( 对象名2 );
与前面定义对象的不同点在于: 括号中不再是参数,而是一个对象;
在建立一个新的对象的时候,调用一个特殊的构造函数 : 复制构造函数;
Box :: Box(const Box &b)
{
height = b.height;
...
...
}
由于在括号中为一个对象,此时在定义一个新的对象的时候就调用复制构造函数;
注:
用户可以自己定义一个复制构造函数,否则系统将自动生成一个复制构造函数,仅仅复制数据;
C++ 还提供一种用赋值号代替:
Box box1 = box2; 一般形式 : 类名 对象名 1 = 对象名 2;
其作用都是调用复制构造函数;
注:
对象的赋值和对象的复制是两个不同的概念:
对象的赋值时建立在两个对象都已经存在的基础之上,是 = 运算符的重构;
而对象的复制是一个对象从无到有的过程,需要调用复制构造函数;
静态成员:
有时人们希望有某个数据为所有对象所共有(如: 平均分,n个总和等),这样可以实现数据公有;
例:
n 个学校除了教学楼,实验室等,公有一个运动场;
1. 静态数据成员:
静态数据成员是一种特殊的数据成员,以关键字static 开头;
例:
class Box
{
public:
int volume();
private:
static int height;
int ...;
...
};
此时 height 在各个对象中的值都是一样的,所有的对象都可以引用它;
说明:
1. 静态成员变量的值在所有对象中都是一样的,所以对象都可以引用他;
2. 静态的数据成员在内存中只占一个存储空间(与函数类似);
3. 如果改变静态数据成员的值,则在所有对象中的值都将改变;
4. 静态成员变量是在所有对象之外单独开辟空间,只要在类中定义了,即使不定义对象,也分配空间;
5. 如果在一个函数中定义了静态变量,在函数结束时,不随对象的撤销而释放!!
静态数据成员是在程序编译时被分配空间的,到程序结束时才释放空间;
6. 静态数据成员可以初始化,但是必须在类体外进行初始化!!!
例:
int Box :: height = 10; // 表示对静态数据成员初始化;
一般形式 : 数据类型 类名 :: 静态数据成员名 = 初值; (不必再初始化语句中加入static)
注:
不能够用参数初始化表对静态数据成员变量初始化:
Box(int h,int w,int len) : height(h){} // 错误!
7. 静态数据成员既可以通过对象名引用(a.height = 10) ,也可以用类名引用 (Box :: height = 10);
例:
class Box
{
public:
Box(int l,int w);
int volume();
static int height;
int length;
int width;
};
Box :: Box(int l,int w)
{
length = l;
width = w;
}
int Box :: volume()
{
return (length * width * height);
}
int Box :: height = 10;
int main()
{
Box a(10,20),b(20,20);
cout << a.height << endl;
cout << b.height << endl;
cout << Box :: height << endl;
cout << "The volume of the box is :" << a.volume() << endl;
return 0;
}
注: 1. 即使在没有定义对象的情况下,也是可以通过类名引用公用的静态变量!!因为静态数据成员不是属于某个对象,而是属于类的!!
2. 静态成员函数:
成员函数也可以定义为静态的,在声明前加上 static 即可;
如:
static int volume();
和静态成员相同,静态成员函数也是类的一部分,可以用类名或对象名调用:
如:
Box :: volume(); || a.voulume();
与静态成员不同,静态成员函数的作用是为了能够处理静态数据成员;
当调用一个对象的成员函数(非静态函数)时,系统会把该对象的起始地址赋值给this指针。
但是静态成员函数并不属于某一对象,它与任何对象都无关,所以没有this指针!!
所以,静态与非静态函数的区别在于静态成员函数没有this指针!
静态成员函数不能够访问非静态变量!!
然而静态成员函数可以直接引用本类中的静态数据成员,因为他们同属于类的,可以直接引用!
所以一般静态成员函数是用来访问静态数据成员,而不访问非静态成员;
例:
一个静态成员函数中有:
cout << height << endl; // height 为 static 正确;
cout << width << endl; // width 是非静态成员变量,错误!
注:
并不是静态成员函数一定不能访问非静态成员,只是不能默认访问!
但是还是可以用加对象名和成员运算符进行访问!!
例:
Student :: Student()
{
cout << "Please Input the data : (number,age,score)" << endl;
cin >> num >> age >> score;
cout << "Data is loaded successfully!" << endl;
}
float Student :: Average() // 定义静态成员变量,只在声明时有 static 、
{
return (sum/count);
}
void Student :: Total()
{
sum += score;
count++;
}
int Student :: count = 0;
float Student :: sum = 0.0;
int main()
{
class Student stu[3] =
{
Student(1001,18,96.5),
Student(1002,19,94),
Student(1003,18,92)
};
cout << "Please Input the number of the Student :" << endl;
int n;
cin >> n;
for(int i = 0; i < n; i++)
{
stu[i].Total();
}
cout << "The average of the score is :" << stu[0].Average() << endl;
cout << "The average of the score is :" << stu[1].Average() << endl;
cout << "The average of the score is :" << stu[2].Average() << endl;
cout << "The average of the score is :" << Student :: Average() << endl;
return 0;
}
友元 :
在一个类中有 public 和 private 之分,在类外可以访问公用成员,而只有本类的函数可以访问私有;
对于友元 friend 可以访问声明与其有好友关系的类中的数据成员;
1. 友元函数:
如果在类外定义了一个函数(不属于本类成员函数,可以是别的类成员或非成员函数):
在本类进行声明时,此函数就成为本类中的友元函数;
一个类的友元函数可以访问这个类的私有成员;
1. 把普通函数声明为友元函数:
class Time
{
public:
Time(int ,int ,int );
friend void Display(Time &); // 定义为友元函数;
private:
int hour;
int minute;
int sec;
};
Time :: Time(int h,int m,int s)
{
hour = h;
minute = m;
sec = s;
}
void Display(Time &t)
{
cout << t.hour << ":" << t.minute << ':' << t.sec << endl; // 输出一个 Time 型的数据;
}
int main()
{
Time t1(12,35,40);
Display(t1); // 因为是友元函数,所以可以使用 t1 里的数据成员;
return 0;
}
2. 友元成员函数:friend函数不仅可以是一般函数, 还可以是另一个类中的成员函数:
class Date; // 对 Date 类型的提前引用声明;
class Time
{
public:
Time(int ,int ,int );
void Display(Date &);
private:
int hour;
int minute;
int sec;
};
class Date
{
public:
Date(int ,int ,int );
friend void Time :: Display(Date &);
private:
int month;
int day;
int year;
};
Time :: Time(int h,int m,int s) : hour(h),minute(m),sec(s) {}
void Time :: Display(Date &d)
{
cout << d.year << '/' << d.month << '/' << d.day << endl;
cout << this->hour << ":" << this->minute << ":" << this->sec << endl;
}
Date :: Date(int y,int m,int d) : month(m),day(d),year(y) {}
int main()
{
Time t1(12,30,15);
Date d1(2018,1,12);
t1.Display(d1);
return 0;
}
说明:在本例中定义了两个类 Data 和 Time。第三行是对Data类的提前声明,因为在Time需要提前引用Data的内容;
而对 Data 的定义在 Time 之后,并且在 Data 的定义中又引用了 Time 所以也不能仅把定义调换位置;
提前引用:
即在正式声明一个类以前,先声明一个类名,表示此类将在稍后声明;
注: 但是不能在提前声明之后,定义一个对象;(必须在正式声明之后才可以定义对象);
3. 一个函数可以被多个类声明为“朋友”,这样可以引用多个类中的私有数据;
友元类:
不仅可以将一个函数声明为一个类的朋友,也可以将一个类( 类B )声明为另一个类的朋友( 类A )。
这时 B就是 A 的友元类,友元类B 的所有函数都是类 A 的友元函数,可以访问 A 的所有成员;
定义方法:
在类 A 的定义体中用以下语句声明:
friend B;
一般形式 : friend 类名;
例: 可以把 Time类,声明为 Data 类的友元类,这样Time的所有函数都可以访问Data
说明:
1. 友元的关系是单向的而不是双向的! 如果仅仅声明了 B为A 的友元类,A 中的函数不可以访问B ;
2. 友元的关系不能传递, B 是 A 的友元 ,C是 B的友元,不能表示 C 是 A 的友元;
应当在 A 中 再次声明 C是A 的友元;
3. 一般情况下, 一般不声明友元类而是声明友元函数,这样安全一些;
类模板:
C++ 中允许使用函数模板,对于功能相同而数据类型不同的一些函数,不必一一去定义;
可以定义一个可对任何数据类型进行操作的函数模板,在调用时系统会根据实参的类型,
取代模板的类型参数,得到具体的参数;
对于类的声明,有时也仅仅是数据类型的不同,此时可以使用类模板:
如:
class Compare_int
{
public:
Compare(int a,int b)
{
x = a; y = b;
}
int max()
{
return ( (x > y) ? x : y );
}
int min()
{
return ( (x < y) ? x : y );
}
private:
int x,y;
};
其作用是对两个整数进行比较,结果可以用 max 和 min 函数来表述。
而对于浮点型作比较,则需要用另外一个类声明:
class Compare_float
{
public:
Compare(float a,float b)
{
x = a; y = b;
}
int max()
{
return ( (x > y) ? x : y );
}
int min()
{
return ( (x < y) ? x : y );
}
private:
float x,y;
};
此时可以声明一个 通用的类模板, 他可以有一个或者多个虚拟的类型参数;
如:
template <typename numtype>
class Compare
{
public:
Compare(numtype a,numtype b)
{
x = a; y = b;
}
numtype max()
{
return ( x > y ) ? x : y;
}
numtype min()
{
return ( x < y ) ? x : y;
}
private:
numtype x,y;
};
说明:
类模板 和之前的声明有所不同:
1. 声明类模板时要增加一行; template< typename XXX >;
2. 原有的类型名 int 换成虚拟类型参数名 typenum。在建立对象的时候,如果指定float 型,则
系统会用 float 取代所有的 typenum;
类模板的使用:
先回顾一下利用类来定义对象的方法:
Compare_int cmpl(4,7); // Compare_int 为一个类名;
用类模板定义一个对象的方法与之类似,但是不能直接写为:
Compare cmpl(4,7); // 错误!;
而必须用实际的类型名来指代虚拟的类型:
Compare <int> cmpl(4,7);
即在模板名后的尖括号中指定实际的类型名;
在进行编译时,编译系统就用 int取代模板中的类型参数 typenum;
例:
声明一个类模板,实现不同数据的比较,求最大最小值;
template <typename numtype>
class Compare
{
public:
Compare(numtype a,numtype b)
{
x = a;
y = b;
}
numtype max()
{
return ( (x > y) ? x : y );
}
numtype min()
{
return ( (x < y) ? x : y );
}
private:
numtype x,y;
};
int main()
{
Compare <int> cmpi(3,7);
cout << "The max of the int is :" << cmpi.max() << endl;
cout << "The min of the int is :" << cmpi.min() << endl;
Compare <float> cmpf(45.2,45.22);
cout << "The max of the float is :" << cmpf.max() << endl;
cout << "The min of the float is :" << cmpf.min() << endl;
Compare <char> cmpc('a','A');
cout << "The max of the char is :" << cmpc.max() << endl;
cout << "The min of the char is :" << cmpc.min() << endl;
return 0;
}
说明:
上面的成员函数是在类内定义的,如果改在类模板外定义,则不能用一般的定义成员函数的形式:
numtype Compare :: max(){ ... } // 错误;
而应当写成类模板的形式:
template <class numtype>
numtype Compare <numtype> :: max()
{
return ( x > y ) ? x : y;
}
所以定义一个类模板的一般形式为:
1. 定义一个类:
template <class XXX>
class Compare
{
...;
};
2. 用类模板定义一个对象:
类模板名 <实际数据名> 对象名(实参表列);
3. 类模板外定义成员函数:
函数类型 类模板名 <虚拟类型参数> :: 成员函数名(函数形参表列) {...}
说明:
1. 类模板的类型参数可以有一个或者多个,每个类型前都必须加 class :
template<class T1, class T2>
class someclass
{...}
在定义对象时分别代入实际的类型名:
someclass <int ,double> obj;
2. 使用类模板时要注意其作用域,只能在有效区域内定义对象;
3. 模板可以有层次,一个类模板可以作为基类,派生出派生模板类;
例:
#include<iostream>
#include<string>
#include<iomanip>
using namespace std;
template <typename numtype>
class Compare
{
public:
Compare(numtype a,numtype b) : x(a),y(b) {}
numtype max();
numtype min();
private:
numtype x,y;
};
template <typename numtype>
numtype Compare <numtype> :: max()
{
return (( x > y ) ? x : y);
}
template <typename numtype>
numtype Compare <numtype> :: min()
{
return (( x < y ) ? x : y);
}
int main()
{
Compare <int> cmpi(3,7);
cout << "The max of the int is :" << cmpi.max() << endl;
cout << "The min of the int is :" << cmpi.min() << endl;
Compare <float> cmpf(45.2,45.22);
cout << "The max of the float is :" << cmpf.max() << endl;
cout << "The min of the float is :" << cmpf.min() << endl;
Compare <char> cmpc('a','A');
cout << "The max of the char is :" << cmpc.max() << endl;
cout << "The min of the char is :" << cmpc.min() << endl;
return 0;
}
注: 每一个模板都要在声明一次!