在这段繁忙而又充实的网课生活中,不知不觉已经过去了六周的时间,在这六周的时间里,我们主要学习了递归算法,结构体,指针和引用,类和对象及运算符重载的问题。接下来让我们回顾一下相关知识。
(一).递归算法
1. 定义:
程序直接或间接调用自身的编程技巧称为递归算法。直接或间接调用自身的函数称为递归函数。它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
2. 递归的基本思想:问题层层分解
把一个不能或不好解决的大问题转化为一个或几个小问题,再把这些小问题进一步分解成更小的小问题。
3. 递归的关键:在于找出递归定义和递归终止条件。
4. 递归算法解题步骤:
1)分析问题、寻找递归:找出大规模问题与小规模问题的关系,这样通过递归使问题的规模逐渐变小。
2)设置边界、控制递归:找出停止条件。
3)设计函数、确定参数:设计函数体中的操作及相关参数。
·应用:
1. 求1 - 100的和
递归:fn ( n ) = n+fn ( n-1)
终止条件:fn(1)=1
递归代码:
function fn(n) {
if (n <= 1) return 1;
else
return n + fn(n - 1);
}
2. 求最大公约数(欧几里德算法)
递归:gcd ( m, n ) = gcd ( n, m % n )
终止条件:gcd(m,0)=m
递归代码:
GCD(m,n) // 约定m>n
{ if (n==0) return(m);
else return (GCD(n,m mod n));
}
3.快速幂
求 a^b 的值(快速幂)
例如当b==5时,
a^5=a*(a^4)
=a*(a^2*a^2)
=a*((a*a)*(a*a)
typedef long long ll;
ll binaryPow(ll a, ll b){
if(b == 1)
return a;
else if(b % 2 == 1) //如果是奇数次幂就-1
return a * binaryPow(a, b - 1) ;
else{
ll num = binaryPow(a, b/2) ; //优化
return num * num ;// 不直接写成return binaryPow(a, b/2, m) * binaryPow(a, b/2, m)
}
4.全排列
·先把高位排好,再关注少一位的排列情况;
·高位有若干种情况,就需要将问题化为多个子问题。每个子问题都与高位的排列有关;
·要列举出所有高位的情况,以确定子问题;
·需要离散枚举!
全排列问题的递归算法
//产生从元素k~m的全排列,作为前k—1个元素的后缀
void Perm(int list[], int k, int m)
{
//构成了一次全排列,输出结果
if(k==m)
{
for(int i=0;i<=m;i++)
cout<<list[i]<<" ";
cout<<endl;
}
else
//在数组list中,产生从元素k~m的全排列
for(int j=k;j<=m;j++) //(m-k+1)种情况
{
swap(list[k],list[j]); //第k位上的数据暂时放在第j位
Perm(list,k+1,m); //对剩余元素递归调用,做全排列
swap(list[k],list[j]); //不能影响下次取值,二次交换,恢复现场。
}
}
5. 半数集问题
给定一个自然数n,由n开始可以依次产生半数集set(n)中的数如下。
(1) n包含于set(n);
(2) 在n的左边加上一个自然数,但该自然数不能超过最近添加的数的一半;
(3) 按此规则进行处理,直到不能再添加自然数为止。
例如,set(6)={6,16,26,126,36,136}。
半数集set(6)中有6个元素。
注意半数集是多重集。
对于给定的自然数n,编程计算半数集set(n)中的元素个数。
int comp(int n)
{
int ans=1;
if (n>1) for(int i=1;i<=n/2;i++)
ans+=comp(i);
return ans;
}
改良版:记忆式搜索
int a[1001];
int comp(int n)
{
int ans=1;
if(a[n]>0)return a[n]; //已经计算
for(int i=1;i<=n/2;i++)
ans+=comp(i);
a[n]=ans; //保存结果
return ans;
}
(二)标准库类型string
1. string 表示可变长度的字符序列,字符串是对象。
string 类支持字符串对象的各种操作:各种初始化方式;字符串之间的复制、比较、连接;查询字符串长度和判断字符串是否为空;访问字符串中的单个字符。
使用string 类要包含头文件<string>
2.初始化
//默认初始化:
string s1; //s1是一个空串,没有任何内容
//拷贝初始化:把=右边的初始值复制到左边新创建的对象中
string s2 = s1; // s2是s1的副本
string s3 = "hello"; //s3是这个字符串字面值的副本
//直接初始化:初始值可以有一个或多个
string s4("welcome"); //s4是这个字符串字面值的副本
string s5(5, 'a'); //s5的内容是aaaaa
3. 读取String对象
(1)使用标准库中iostream可以读写string对象
(2)可以用循环读取未知数量的string对象
输入操作符返回输入流对象,如果输入流对象处于有效状态,表示没有遇到文件结束或非法输入。
//读取输入流中的单词,直到文件结束
string word;
while(cin >> word)
cout << word << endl;
(3).getline()函数
两个参数:输入流对象和存放读入字符串的string对象
从指定输入流中读取内容,遇到换行符为止;将所读内容存入指定的string对象中,流中的换行符被读取并丢弃
返回参数输入流对象
//每次读取一行文本,直到文件结束
string line;
while(getline(cin, line))
cout << line << endl;
4. 判断stirng对象是否为空
empty()函数判断string对象是否为空,返回一个布尔值
stringObj.empty()
//每次读取一行文本,输出非空的行
string line;
while(getline(cin, line))
if(!line.empty()) //line不为空,输出
cout << line << endl;
5. 获取string对象的长度
·size()函数返回string对象的长度,即对象中字符的个数,返回的长度是string::size_type类型。
·//每次读取一行文本,输出长度超过80个字符的行
string line;
while(getline(cin, line))
if(line.size() > 80)
cout << line << endl
·//字符串长度的类型
string line = "hello";
string::size_type len1 = line.size();
auto len2 = line.size();
decltype(line.size()) len3 = 0;
6. 比较string对象
·可以用关系运算符比较两个字符串对象
·两个string相等意味着它们的长度相同,并且所包含的字符也完全相同
·字符串的大小关系依照字典顺序定义且区分大小写字母
string s1 = "hello";
string s2 = "hello world"; // s2 > s1
string s3 = "Hello"; // s3 < s1, s3 < s2
7. string对象的赋值和连接
·允许把一个string对象的值赋给另一个string对象
·也可以为string对象赋一个字符串字面值常量
string s1 = "hello", s2;
s2 = s1;
s1 = "C++ Programming Language";
·两个字符串可以直接用运算符“+”连接,结果得到一个新的string对象,"+"运算符要求至少有一个运算对象是string
·复合赋值运算符“+=”则将右操作数的内容追加到左操作数的后面
string s1 = "hello, ", s2 = "world! " ;
string s3 = s1 + s2;
s1 += s2;
string s4 = s1 + "\n"; //正确
string s5 = "hello" + "\n"; //错误
string s6 = s1 + " world" + "\n"; //正确
string s5 = "hello" + "," + s2; //错误
8. string对象和C风格字符串
·字符串字面值不是string类型,而是const char*类型
·string对象和C风格字符串的转换:将C风格的字符串直接赋给string对象,反之不可。
·用string对象的c_str()操作可以返回一个表示该string对象内容的C风格字符串,结果为const char*类型,即C风格字符串的首地址。
string s1 = "If you really want it.";
int x = strlen(s1); //Error
x = strlen(s1.c_str()); //OK
9. 随机访问string中的字符
·下标运算符可以访问string对象中指定位置的字符,string对象s的下标范围从0到s.size()-1
//将s中的第一个词改成大写形式
string s = "Hello, World!";
int index = 0;
while (index != s.size() && !isspace(s[index])) {
s[index] = toupper(s[index]);
++index;
}
cout << s << endl; //输出:HELLO, World!
·应用:
1.
#include <iostream>
#include <string>
using namespace std;
int main(){
string s1, s2; //创建两个空字符串对象
string s3 = "Hello, World!"; //创建s3,并初始化
string s4("I am ");
s2 = "Today"; //赋值
s1 = s3 + " " + s4; //字符串连接
s1 += " 5 "; //末尾追加
cout << s1 + s2 + "!" <<endl; //输出字符串内容
cout <<"Length of s1 is :" << s1.size() << endl;
//逐个输出s1中的字符
for (size_t i = 0; i < s1.size(); ++i)
cout << s1[i] <<" ";
}
2. 将0到15之间的十进制数转换成对应的十六进制形式。
#include <iostream>
#include <string>
#include <cstddef>
using namespace std;
int main(){
const string hexdigits = "0123456789ABCDEF";
cout << "Enter a numbers between 0 and 15: " << endl;
int n;
cin >> n;
if (n < hexdigits.size()) //检查下标范围
cout <<"Hex number is: "<< hexdigits[n] << endl;
else
cout <<"Invalid input." << endl;
}
(三)结构体
1.定义:C++ 中的结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,也叫结构。
(1)使用结构体,必须要先声明一个结构体类型,再定义和使用结构体变量。结构体类型的声明格式如下:
struct 类型名{
数据类型1 成员名1;
数据类型2 成员名2;
…
};
(2). 定义结构体变量
struct 结构体类型名 变量名列表;
也可以把结构体类型声明和变量定义合在一起,格式如下:
struct 类型名{
数据类型1 成员名1;
数据类型2 成员名2;
…
} 变量名;
2. . 结构体的使用
·结构体变量具有以下特点:
(1)可以对结构体变量的整体进行操作。
例如:swap(a[i],a[j])
(2)可以对结构体变量的成员进行操作。
引用结构体变量中成员的格式为:
结构体变量名. 成员名
(3)结构体变量的初始化方法与数组类似。
3. 成员函数
在 C++ 中,允许在结构中定义函数,该函数称为“成员函数”。描述形式如下:
struct 结构名 {
数据成员
成员函数
};
4.枚举
(1)枚举类型定义了一组命名的整数常量,以提高代码的可读性
enum TrafficLight{red,yellow,green};
TrafficLight枚举类型定义了3个常量:0,1,2 分别和名字red,green以及yellow关联。
TrafficLight是一个枚举类型,可以用来定义枚举变量,变量的值只能是枚举成员。
TrafficLight stop=red;
(2) 可以指定枚举成员的值,编译器会给未指定值的枚举成员赋予相邻值的下一整数值.
enum ShapeType
{circle=10, square=20, rectangle};
//rectangle成员的值是21
·可以使用未命名的枚举类型定义常量
enum {False, True};
//定义了两个常量False和True,值分别是0和1
(3)枚举类型在必要时,如参与算术运算时,会被自动提升为算术类型。
enum small{zero, one}; //int
enum large{two = 2, big = 0x8000000000}; //long
枚举的成员名字是不可打印的,输出的是它所表示的整数值。另外,不能使用枚举成员进行迭代,C++不支持枚举成员之间的前后移动(递增或递减)
·应用
- 年龄排序:
【问题描述】
输入 n 个学生的信息,包括姓名、性别、出生年月。要求按年龄从小到大依次输出这些学生的信息。数据保证没有学生同年同月出生。
【输入格式】
第一行一个整数 n,表示学生人数,n≤100。
接下来 n 行,每一行依次输入学生的姓名、性别、出生年份、出生月份。
【输出格式】
按年龄从小到大,一行输出一个学生的原始信息。
#include<bits/stdc++.h>
using namespace std;
struct stu{
string name;
string sex;
int year,month;
};
const int MAXN = 110;
stu a[MAXN];
int main(){
int n;
cin >> n;
for(int i = 1; i <= n; i++)
cin >> a[i].name >> a[i].sex >> a[i].year >> a[i].month;
for(int i = 1; i <= n; i++)
for(int j = i+1; j <= n; j++)
if(a[i].year < a[j].year || a[i].year == a[j].year && a[i].month < a[j].month)
swap(a[i],a[j]);
for(int i = 1; i <= n; i++){
cout<< a[i].name << ” ” << a[i].sex << ” ” ;
cout<< a[i].year << ” ” << a[i].month << endl;
}
return 0;
}
- 运算符重载
“运算符重载”常用于解决结构体或自定义数据类型的加法、减法等特殊含义的运算。运算符重载的一般格式为:
类型名 operator 运算符 (const 类型名 变量)const{
…
}
·本题需要把若干时间(时、分、秒)相加,但又不是普通的加法运算。所以,可以利用运算符重载,另外定义一个“+”运算,实现对两个结构体变量的加法。
#include<bits/stdc++.h>
using namespace std;
struct worktime{// 声明一个结构体类型 worktime 记录学生完成作业的时间
int hr,minut,sec; // hr,minut,sec 分别代表时分秒
worktime operator +(const worktime x)const{ // 对 + 进行重新定义
worktime tmp;
tmp.sec = (sec + x.sec) % 60;
tmp.minut = (minut + x.minut + (sec + x.sec) / 60) % 60;
tmp.hr = hr + x.hr + (minut + x.minut + (sec + x.sec) / 60) / 60;
return tmp;
}
};
int main(){
worktime stu,sum;
int n;
cin >> n;
sum.hr = sum.minut = sum.sec = 0;
for(int i = 1; i <= n; i++){
cin >> stu.hr >> stu.minut >> stu.sec;
sum = sum + stu;// 两个结构体 sum 和 stu 通过重载 + 运算符进行加法运算
}
cout << sum.hr << ” hour ” << sum.minut << ” minute ” << sum.sec << ” second ” ;
return 0;
}
- 身高排序(包含数据成员和成员函数)
【问题描述】输入 n 个学生的信息,每个学生信息包括姓名、身高、学号。编程输出身高最高的学生的信息。
【输入格式】第 1 行一个正整数 n,表示学生个数,n≤100。
以下 n 行,每一行依次输入学生的姓名、身高、学号。
【输出格式】输出最高的学生信息,如存在身高一样的请输出学号小的那个同学。
#include<bits/stdc++.h>
using namespace std;
struct stu{
string name;
int heigh;
int num;
void input(){
cin >> name >> heigh >> num;
}
void output(){
cout << name << “ “ << heigh << “ “ << num << endl;
}
};
stu a[110];
int main(){
int n;
stu maxn;
maxn.heigh = maxn.num = 0;
cin >> n;
for(int i = 1; i <= n; i++){
a[i].input();
if(a[i].heigh > maxn.heigh) maxn = a[i];
else if(a[i].heigh == maxn.heigh && a[i].num < maxn.num) maxn = a[i];
}
maxn.output();
return 0;
}
(四)指针和引用
1.内存地址和间接访问
(1)内存地址
程序运行时,代码和需要的数据都被存储在内存中。内存是有序的字节序列,每个字节都有唯一的地址,使用该地址可以确定字节的位置,用以存储和获取数据。
(2)直接访问和间接访问
直接:通过变量的名字直接访问为程序中定义的变量分配的内存单元,存取变量的值。
间接:使用变量的内存地址找到存放数据的单元,间接访问其中的内容。
2. 指针的特点
·指针持有一个对象的地址,称为指针“指向”该对象
·通过指针可以间接操纵它指向的对象
·定义指针变量的语法
·每个指针都有相关的类型,要在定义指针时指出:类型 *指针变量;
- 取地址运算符“&”
指针存放指定类型对象的地址,要获取对象的地址,使用取地址运算符“&”
int ival = 120;
int *pi = &ival;
// pi存放int变量ival的地址
// 或者说pi指向ival
char ch = 'a', *pc = &ch;
// pc指向字符型变量ch
- 指针解引用运算符“*”
如果指针指向一个对象,则可以通过指针间接访该对象,使用指针解引用运算符“*”
int x = 100, y = 20;
int *pi = &x;
*pi = y;
// 间接操作pi指向的x,即x = y
- 指向对象的指针
·指向一个对象的指针有两个存储单元与之相关:
-
- 一个是指针自己的存储单元,里面存放着所指对象的地址;
- 另一个就是指针指向的对象的存储单元,里面存放该对象的值。
·可以定义存放指针对象的地址的指针:
int ival = 1024;
int *pi = &ival;
int **ppi = π //ppi是指针的指针,存放pi的地址
·举个例子理解一下:
#include <iostream>
using namespace std;
int main( )
{
int ival=1024;
int *pi=&ival;
cout << " sizeof(pi):" << sizeof(pi) << endl; //指针在内存中所占大小
cout << " sizeof(ival):" << sizeof(ival) << endl; //ival在内存中所占大小
cout << " &pi:" << &pi << endl; //指针在内存中的地址
cout << " pi:" << pi << endl; //指针中存放的内容,即ival的地址
cout << " &ival:" << &ival << endl; //ival的地址
cout << " *pi:" << *pi << endl; //指针所指内存中存放的内容,即ival的值
cout << " ival:" << ival << endl; //ival的值
}
- 指针的类型:指针的类型即指针指向的类型
·每个指针都有相关的类型,需要在定义指针时指出:type* pointer
·指针的类型指出了如何解释该内存地址保存的内容,以及该内存区域应该有多大,不同类型指针的表示方法和保存的地址值并没有分别,区别只是指针指向的对象类型不同。
- 指针的值
·指针不能保存非地址值,也不能被赋值或初始化为不同类型的地址值。
int ival = 100;
int *pi = &ival; // pi 被初始化为ival的地址
int *pi2 = ival; // 编译错误,ival不是地址
double dval = 1.5;
pi = &dval; // 编译错误
pi2 = 0; // 正确:pi2是空指针
- 空指针:指针值为0时是一个空指针,即不指向任何对象的指针。
·表示空指针的2种方法
-
- 0
- 预处理常量NULL <cstdlib>
// 生成空指针的2种方法
int *p1 = 0;
int *p2 = NULL;
//不能写成下面的样子:
int zero = 0;
int *p4 = zero;
- 使用指针的注意事项
·定义指针时,应该对指针进行初始化
·指针的运算:
同类型的指针可以进行相等(==)或不相等(!=)的比较操作,比较的结果是布尔类型
指针可以进行加或减整数值的算术运算
自增、自减运算适用于指向数组元素的指针
- 通用指针void*
·可以持有任何类型的地址值,即通用指针
·相关的值是个地址,但是该地址保存的对象类型不知道
·不能操纵void指针指向的对象,只能传送该地址值或者和其他地址值进行比较
·不允许void指针到其他类型指针的直接赋值
- 指针的应用
·构建链式的数据结构,如链表和树;
·管理程序运行时动态分配的对象;
·作为函数的参数。
(五)存储空间分配策略
1. 静态(编译时)分配空间,编译器在处理程序源代码时分配内存;
效率高,灵活性差,运行前就要知道程序需要的内存大小和类型。
动态(运行时)分配空间,程序运行时调用运行时刻库函数来分配内存;
占用程序运行时间,更灵活。
2.动静态区别:
·静态对象是有名字的变量,可以直接对其进行操作;动态对象没有名字,要通过指针间接地对它进行操作。
·静态对象的空间分配与释放由编译器自动处理,动态对象的空间分配与释放必须由程序员显式地管理。
3. 程序使用动态内存的原因
程序不知道自己需要使用多少对象
程序不知道所需对象的准确类型
程序需要在多个对象间共享数据
4. 动态存储空间管理
·堆(heap)、自由存储区、动态存储区:
系统为所有程序提供了一个运行时可用的内存池,这个内存池被称为程序的自由存储区或堆
·动态内存管理方法:C++通过new和delete运算符进行动态存储空间的管理。
5. new运算符
·在堆上动态分配空间,创建对象,并返回对象的地址,一般将new返回的地址保存在指针变量中,以便间接访问堆上的对象。
·new表达式的三种形式
·分配单个对象:new 类型 或者 new 类型(初始值)
在堆上分配特定类型的单个对象,并返回其地址
int* ip1 = new int;
//在堆上分配一个int类型的对象,返回它的地址
*ip1 = 512;
//堆上分配的这个对象只能通过指针间接操作
int* ip2 = new int(100);
//在堆上分配一个int对象,
//初始化为100,返回其地址
·分配多个连续存储的对象:new 类型[数组大小]
在堆上分配指定类型和大小的数组(连续内存空间),返回数组首地址
int* ipa = new int[100]; //在堆上分配一个大小为100的int数组并返回数组的首地址
不能对数组进行显式的初始化,数组大小不必是常量,是数组元素的个数,不是字节数。用new分配数组时,并未得到一个数组类型的对象,而是得到一个数组元素类型的指针。
·定位new,在指定位置分配空间:new (指针) 类型;
定位new在指针指向的空间中创建一个指定类型的对象,程序员可以预先分配大量的内存,以后通过定位new表达式在这段内存中创建对象。使用定位new,必须包含标准库头文件<new>。
#include <new>
char* buf = new char [1000];
//预分配一段空间,首地址在buf中保存
int main(){
int* pi = new (buf) int;
//在buf中创建一个int对象,此时不再重新从堆上分配空间
}
6. delete运算符
·new运算符分配的空间用delete运算符释放,释放new分配的单个对象的delete形式
delete 指针;
int* ip = new int;
... //不再使用这个int对象时,释放内存
delete ip;
//释放指针指向的int对象,将空间归还给动态存储区
·释放new分配的数组的delete形式
delete[] 指针;
int* pa = new int[100];
... //不再使用这个数组时,释放内存
delete[] pa;
//释放指针pa指向的数组,将空间归还给动态存储区
·定位new没有对应的delete表达式
(六)引用
1. 引用类型:
·引用又称为别名,它可以作为对象的另一个名字;
·通过引用可以间接地操纵对象;在程序中,引用主要用作函数的参数。
2. 引用的定义和初始化
·引用由类型标识符和一个说明符(&)来定义:
type& refVariable = leftValue; 类型 &引用名 = 初始值;
引用必须被初始化,初始值是一个有内存地址的对象。
·引用的初始化和一般变量的初始化不同
一般在初始化变量时,初始值会被复制到新建的对象中,定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。
一旦初始化完成,引用将和它的初始值对象一直绑定在一起,因为无法令引用重新绑定到另外一个对象,所以引用必须初始化。
3. 引用关系
·引用一旦初始化,就不能再指向其他的对象,对引用的所有操作都会被应用在它所指向的对象上。
·引用的初始化和赋值不同:初始化时引用被“绑定到”一个对象;赋值时,引用被作为所绑定对象的别名。
int x = 100, y = 20;
int &r = x; // r是x的引用
r = y; // r不会变成y的引用,而是x = y
4. const限定指针
·指向const对象的指针(非const ):const type *cp; 或者type const *cp;
cp是指向常量的指针,它所指向的内存中的内容不可以改变,即*cp的值不能改变。
const int ival = 1024;
int *pi = &ival;
//错误:试图将一个const地址赋值给一个非const指针
const int ival = 1024;
const int *pi = &ival; //OK
//或者这样写:
int const *pi = &ival; //OK
*pi = 500;
//错误:因为*pi是一个const int
·指向非const对象的const指针type* const cp = initAddressValue;
cp是常量指针,初始化后值不能改变,指向固定的单元。
int ival =1024;
const int *pi = &ival;
ival = 500;
//OK,ival没有被限定为const,可以改变
*pi = 500;
//错误: 不可以通过pi改变ival,因为pi是const int*
·指向const对象的const指针:const type* const cp = initAddressValue;
const int ival = 5;
const int* const pi = &ival;
//pi是一个指向const对象的const指针
第一个const限定int,表示指针指向的单元是常量;
第二个const限定pi,表示指针的值也是一个常量;
指针pi所在内存的值不允许改变,它所指向内存的值也不能改变。
5. begin()和end()
·库函数begin()和end(),让指针在数组上的使用更简单更安全
在头文件<iterator>中定义:
begin(数组名) 返回指向数组第一个元素的指针
end(数组名) 返回指向数组最后一个元素的下一个位置的指针
//在数组arr 中查找第一个负数:
int *pb = begin(arr), *pe = end(arr);
while(pb != pe && *pb >= 0)
++pb;
//逐个输出数组元素的循环
for(int p = begin(arr); p!= end(arr); ++p)
cout << *p << "\t";
6. this指针
·需要显式引用this指针的三种情况
- 在类的非静态成员函数中返回类对象本身或对象的引用的时候,直接使用 return *this,返回本对象的地址时,return this。
#include <iostream>
#include <string>
using namespace std;
class Person{
public:
Person(string n, int a) {
name = n; //这里的 name 等价于this->name
age = a; //这里的 age 等价于this->age
}
int get_age(void) const{ return age; }
Person& add_age(int i) {age += i; return *this; }
private:
string name;
int age;
};
int main()
{
Person Li("Li", 20);
cout<<"Li age = "<< Li.get_age()<<endl;
cout<<"Li add age = "<< Li.add_age(1).get_age()<<endl;
//增加1岁的同时,可以对新的年龄直接输出;
return 0;
}
程序执行结果为:
Li age = 20
Li add age = 21
- 当参数与成员变量名相同时,如this->x = x,不能写成x = x。
#include <iostream>
using namespace std;
class Point
{
public:
int x;
Point ():x(0){}
Point (int a){ x=a; }
void print(){ cout<<"x = "<<x<<endl; }
void set_x(int x){ x = x; }
};
int main(){
Point pt(5);
pt.set_x(10);
pt.print();
return 0;
}
程序执行结果为:
x = 5
若将set_x函数改为:
void set_x(int x) { this->x = x; }
程序执行结果为:
x = 10
(3)避免对同一对象进行赋值操作,判断两个对象是否相同时,使用this指针。
#include <iostream>
using namespace std;
class Location
{
int X,Y; //默认为私有的
public:
void init(int x,int y) { X =x; Y = y;};
void assign(Location& pointer);
int GetX(){ return X; }
int GetY(){ return Y; }
};
void Location::assign(Location& pointer)
{
if(&pointer!=this) //同一对象之间的赋值没有意义,所以要保证pointer不等于this
{ X=pointer.X; Y=pointer.Y; }
}
int main(){
Location x;
x.init(5,4);
Location y;
y.assign(x);
cout<<"x.X = "<< x.GetX()<<" x.Y = "<<x.GetY();
cout<<"y.X = "<< y.GetX()<<" y.Y = "<<y.GetY();
return 0;
}
·联系
1.数组与指针
·使用数组时一般会转换为指针:ia是一个int*类型的指针常量;ia和&ia[0]都表示数组第一个元素的地址,可以使用指针对数组进行访问。
int a[10];
//指针访问数组元素
for(int *p = a; p < a+10; p++)
cout << *p;
2.数组元素和地址
·一维数组元素在内存中按下标顺序依次存放,一维数组a[n]的元素a[i]在内存中地址是a+i。
·多维数组在内存中按行序存储,二维数组a[m][n]的元素a[i][j] 在内存中的地址是a+(i*n+j)
·使用指针访问数组时需要控制指针的范围,确保指针指向数组的元素
for(int *p = a; p < a+10; p++)
(七)类
1. 类的定义
·类是对具有相同属性和行为的一类客观事物的概括描述。是用户自定义的数据类型(程序设计语言角度)
·类的定义包括行为和属性两个部分。属性以数据表示,行为通过函数实现。
class 类名
{
public:
公有数据成员和成员函数;
protected:
保护数据成员和成员函数;
private:
私有数据成员和成员函数;
};
各成员函数的实现;
·注意事项
·类的数据成员可以是其他类的对象,但不能以类自身的对象作为本类的成员,而类自身的指针和引用可以作为类的成员。
·类定义必须以分号“;”结束。
·类与结构体的区别:没有明确指定类成员的访问权限时,C++结构体的成员是公有的,而类的成员是私有的。
2. 成员函数
类的成员函数是实现类的行为属性的成员。一般将成员函数声明为函数原型,在类外具体实现成员函数。
·成员函数的定义
返回值类型 类名::成员函数名(参数表)
{
函数体
}
举个例子:
class Point
{
public:
float GetPointx();
float GetPointy();
void InitPoint(float PointA_x, float PointA_y);
void Move(float New_x, float New_y);
private:
float P1_x,P1_y;
};
void Point::InitPoint(float PointA_x, float PointA_y)
{
P1_x=PointA_x;
P1_y=PointA_y;
}
void Point::Move(float New_x, float New_y)
{
P1_x+=New_x;
P1_y+=New_y;
}
float Point::GetPointx()
{
return P1_x;
}
3. 对象:是类的实例或实体。
·类与对象的关系,如同C++基本数据类型和该类型的变量之间的关系。
·对象的定义:类名 对象名1,对象名2,…,对象名n;
class Point
{
public:
void InitPoint(float PointA_x, float PointA_y);
void Move(float New_x, float New_y);
float GetPointx();
float GetPointy();
private:
float P1_x,P1_y;
};
int main()
{
Point p1,p2;
}
4. 类成员的访问
对象成员的访问包括:
·圆点访问形式:对象名.公有成员;
·指针访问形式:对象指针变量名->公有成员
#include<iostream.h>
class ptr_access {
public:
void setvalue(float a, float b) { x=a; y=b; }
float Getx() {return x;}
float Gety() {return y;}
void print()
{
cout<<"x="<<x<<endl;
cout<<"y="<<y<<endl;
}
private: //私有数据成员
float x,y;
};
int main()
{
float a1,a2;
ptr_access *ptr=new ptr_access;
ptr->setvalue(2,8); //通过指针访问公有成员函数
ptr->print();
a1=(*ptr).Getx();
//通过公有成员函数访问私有数据成员
a2=(*ptr).Gety();
cout<<"a1="<<a1<<endl;
cout<<"a2="<<a2<<endl;
return 0;
}
5. 类定义和使用时应注意:
·在类的定义中不能对数据成员进行初始化。
·类的任何成员都必须指定访问属性,一般将数据成员定义为私有成员或保护成员,将成员函数定义为公有成员。
·类中的数据成员可以是C++语法规定的任意数据类型。
·类的成员可以是其他类的对象,称为类的组合。但不能以类自身的对象作为本类的成员。
·类定义必须以分号“;”结束
class与struct的不同:
class中,成员缺省情况是private。
struct中,成员缺省情况是public。
6. 内联函数
·内联函数作用:减少频繁调用小子程序的运行的时间开销。
·内联函数机制:编译器在编译时,将内联函数的调用以相应代码代替。
·内联函数声明:inline 函数原型
·注:内联函数仅在函数原型作一次声明。适用于只有1 ~ 5行的小函数,不能含有复杂结构控制语句 ,不能递归调用。
class Coord{
public:
void setCoord(int,int);
int getx();
int gety();
private:
int x,y;
};
inline void Coord::setCoord(int a,int b)
{ x=a; y=b;}
inline int Coord::getx(){ return x;}
inline int Coord::gety(){ return y; }
7. 成员函数可以重载
·函数重载:函数名相同,但参数不相同(类型不同,或者个数不同)的一组函数。
·编译器根据不同参数的类型和个数产生调用匹配.函数重载用于处理不同数据类型的类似任务。
8. 构造函数和析构函数
(1)构造函数
·构造函数是用于创建对象的特殊的成员函数,当创建对象时,系统自动调用构造函数
·构造函数的作用是:为对象分配空间;对数据成员赋初值;请求其他资源。
·用户没有定义的构造函数时,系统自动提供缺省版本的构造函数
·构造函数名与类名相同:类名
·构造函数可以重载
·构造函数可以有任意类型的参数,但没有返回类型
(2)析构函数
·析构函数是用于取消对象的成员函数
·当一个对象作用结束时,系统自动调用析构函数
·析构函数的作用是进行对象消亡时的清理工作
·没有用户定义析构函数时,系统提供缺省版本的析构函数
·析构函数名为: ~ 类名
·析构函数没有参数,也没有返回类型
·特点:
① 析构函数与构造函数名字相同,但它前面必须加一个波浪号(~);
② 析构函数没有参数,也没有返回值,而且不能重载。因此在一个类中只能有一个析构函数;
③ 当撤消对象时,编译系统会自动地调用析构函数。
9.默认构造函数
类名::类名(){}
(1) 利用构造函数直接创建对象.其一般形式为:
类名 对象名[(实参表)];
不带参数:当用户没定义构造函数时,调用默认的构造函数;当用户定义了构造函数时,调用无参的构造函数(没有无参构造函数时要出错!系统不会调用默认构造函数)
带实参表:系统按照重载函数匹配原则,调用对应的构造函数;
为类Date建立一个构造函数。
#include <iostream.h>
class Date {
public:
Date(); // 无参构造函数
Date(int y,int m,int d);
void showDate();
private:
int year, month, day;
};
Date::Date() // 构造函数的实现
{ year=0; month=0; day=0; }
Date::Date(int y,int m,int d)
{ year=y; month=m; day=d; }
inline void Date::showDate()
{ cout<<year<<"."<<month<<"."<<day<<endl; }
int main()
{
Date a_date,b_date(2014,3,25);
a_date.showDate();
b_date.showDate();
return 0;
}
(2) 利用构造函数创建对象时,通过指针和new来实现。其一般语法形式为:
类名 *指针变量 = new 类名[(实参表)];
例如: Date *date1=new Date(1998,4,28);就创建了对象(*date1)。
10. 数据成员的初始化
A.使用构造函数的函数体进行初始化
class Date
{
int d, m, y;
public:
Date(int dd, int mm, int yy)
{
d=dd;
m=mm;
y=yy;
}
Date(int dd, int mm)
{
d=dd;
m=mm;
}
}
B.使用构造函数的初始化列表进行初始化
·funname(参数列表):初始化列表
{ 函数体,可以是空函数体 }
·初始化列表的形式: 成员名1(形参名1),成员名2(形参名2),成员名n(形参名n)
class Date
{
int d, m, y;
public:
Date(int dd, int mm, int yy):d(dd),m(mm),y(yy)
{ }
Date(int dd, int mm): d(dd),m(mm)
{ }
}
11. 构造函数的重载( overload)
class Box{
public:
Box();
Box(int ,int , int);
int volume();
private:
int height,width, length;
};
Box::Box()
{ height=10; width=10; length=10;}
Box::Box(int h, int w,int l):height(h),width(w),length(l)
{}
int Box::volume()
{ return width*length*height;}
int main(){
Box box1;
cout<<"The volume is "<<box1.volume();
Box box2(12,30,25);
cout<<"The volume is "<<box2.volume();
return 0;
}
·例题
1. 设计一个point 类
数据成员
点的坐标x,y
成员函数
无参构造函数
有参构造函数
set函数(设置x,y的值)
get_X函数
get_Y函数
display函数
#include <iostream>
#include <cmath>
using namespace std;
class Point{
int x,y;
public:
Point(){}
Point (int a,int b)
{ x=a,y=b; }
void setx(int xx) { xx=x; }
void sety(int yy) { yy=y; }
int getx() { return x; }
int gety() { return y; }
};
- 复制构造函数
·复制构造函数用一个已有同类对象创建新对象进行数据初始化
类名 :: 类名(const 类名 & 引用名 , …);
·复制构造函数的调用:
- 声明语句中用类的一个已知对象初始化该类的另一个对象时。
②当对象作为一个函数实参传递给函数的形参时,需要将实参对象去初始化形参对象时,需要调用复制构造函数。
③当对象是函数的返回值时,由于需要生成一个临时对象作为函数返回结果,系统需要将临时对象的值初始化另一个对象,需要调用复制构造函数。
- 浅复制与深复制
关于浅复制:
●在用一个对象初始化另一个对象时,只复制了数据成员,而没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制。
即:对于复杂类型的数据成员只复制了存储地址而没有复制存储内容
●默认复制构造函数所进行的是简单数据复制,即浅复制
关于深复制:
●通过一个对象初始化另一个对象时,不仅复制了数据成员,也复制了资源的复制方式称为深复制。
●自定义复制构造函数所进行的复制是浅复制
深复制构造函数必须显式定义
深复制构造函数的特点
定义:类名::类名([const] 类名 &对象名);
成员变量的处理:对复杂类型的成员变量,使用new操作符进行空间的申请,然后进行相关的复制操作。
15.常成员
·常数据成员是指数据成员在实例化被初始化后,其值不能改变。
·在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数
(1) 常数据成员
使用const说明的数据成员称为常数据成员。
如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其他函数都不能对该成员赋值。
#include<iostream>
using namespace std;
class Mclass
{ public :
int k;
const int M; //说明常数据成员
Mclass() : M(5) { } //用初始式对常数据成员赋值
void testFun()
{ //M++; //错误,不能在成员函数中修改常数据成员
k++; //可以修改一般数据成员
}
} ;
int main()
{ Mclass t1, t2;
t1.k=100;
//t1.M=123; //错误,不能在类外修改常数据成员
cout<<"t1.k="<<t1.k<<'\t'<<"t1.M="<<t1.M<<endl;
t2.k=200; t2.testFun();
cout<<"&t1.M="<<&t1.M<<endl;
cout<<"t2.k="<<t2.k<<'\t'<<"t2.M="<<t2.M<<endl;
cout<<"&t2.M="<<&t2.M<<endl;
}
例:#include<iostream>
#include<cstring>
using namespace std;
struct Date { int year, month, day ; }; //结构
class Student
{ public:
Student (int y,int m,int d, int num=0, sring name="no name");
void PrintStudent()const; //常成员函数
private:
const int code; //常数据成员
string name;
Date birthday; //结构数据成员
};
- 用带参数的构造函数初始化常数据成员
Student::Student( int y, int m, int d, int num, string name ) : code( num )
{ this->name=name;
birthday.year=y;
birthday.month=m;
birthday.day=d;
}
int main()
{ Student stu1( 1990, 3, 21, 1001, "陈春“ );
stu1.PrintStudent();
Student stu2(1985, 10, 1, 1002, "张庆华“ );
stu2.PrintStudent();
}
16.常对象
·如果在说明对象时用const修饰,则被说明的对象为常对象。
类名 const 对象名[(参数表)];或者 const 类名 对象名[(参数表)];
在定义常对象时必须进行初始化,而且不能被更新。
说明:
(1)C++不允许直接或间接更改常对象的数据成员。
(2)C++规定常对象只能调用它的常成员函数、静态成员函数、构造函数(具有公有访问权限)。
17. 常成员函数
·在类中使用关键字const说明的函数为常成员函数,常成员函数的说明格式如下:
类型说明符 函数名(参数表) const;
·const是函数类型的一个组成部分,因此在函数的实现部分也要带关键字const。
·常成员函数不能更新对象的数据,也不能调用非const修饰的成员函数(静态成员函数、构造函数除外)
#include<iostream>
using namespace std ;
class Simple
{ int x, y ;
public :
void setXY ( int a, int b) { x = a ; y = b ; }
void printXY() { cout << x << "," << y << endl ; }
void constFun ( ) const
{ x ++ ; y ++ ; }//非法
};
18. 静态成员
·类成员冠以static声明时,称为静态成员。
·静态数据成员为同类对象共享。
·静态成员函数与静态数据成员协同操作。
·静态成员不属于某一个单独的对象,而是为类的所有对象所共有
·静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员: 保证在不依赖于某个对象的情况下,访问静态数据成员 。
(1)静态数据成员
·静态数据成员在定义或说明时前面加关键字static,如:
class A
{
int n;
static int s;
};
·对于类的普通数据成员,每一个对象都各自拥有一个副本。(分配不同的存储空间)
·对于静态数据成员,每个类只拥有一个副本 。(在静态存储区分配一个存储空间,对所有对象都是可见的)
·公有访问权限的静态成员,可以通过下面的形式进行访问
类名::静态成员的名字
对象名.静态成员名字
对象指针->静态成员的名字
在静态成员函数内部,直接访问。
·应用:设计包含静态数据成员的类
某商店经销一种货物,货物成箱进,成箱卖出,购进和卖出都是以重量为单位(每箱的重量不同),商店需要记录下存货的总重量。
分析:定义一个货物类,表示一箱货物,类中包含
私有成员 weight
一个静态数据成员total_weight;
定义in函数,表示进货,进货时total_weight的值增加
定义out函数,表示出货,出货时total_weight的值减少
#include <iostream>
using namespace std;
class Goods{
int weight;
//static int total_weight;
public:
static int total_weight;
Goods(int x):weight(x){}
void in() {total_weight=total_weight+weight; }
void out(){total_weight=total_weight-weight; }
void display_store(){ cout<<total_weight<<endl;}
};
int Goods::total_weight=0;
void display_store(){ cout<<Goods::total_weight<<endl;}
int main(){
Goods g1(10),g2(20);
g1.in(); g2.in();
g1.display_store(); g2.display_store(); display_store();
g1.out(); g1.display_store(); display_store();
return 0;
}
(2)静态成员函数
·定义静态成员函数的格式如下:static 返回类型 静态成员函数名(参数表);
·与静态数据成员类似,调用公有静态成员函数的一般格式有如下几种:
类名::静态成员函数名(实参表)
对象. 静态成员函数名(实参表)
对象指针->静态成员函数名(实参表)
·说明:
(1)静态成员函数在类外定义时不用static前缀。
(2)静态成员函数主要用来访问同一类中的静态数据成员。
(3) 私有静态成员函数不能在类外部或用对象访问。
(4)可以在建立对象之前处理静态数据成员。
(5)编译系统将静态成员函数限定为内部连接(在其他文件中不可见)。
(6)静态成员函数中是没有this指针的。
(7)静态成员函数不访问类中的非静态数据成员。如有需要,只能通过对象名(或指向对象的指针)访问该对象的非静态成员。
19. 类的包含(类的组合)
·类的包含是程序设计中一种软件重用技术。即定义一个新的类时,通过编译器把另一个类 “抄”进来。
·当一个类中含有已经定义的类类型成员,带参数的构造函数对数据成员初始化,须使用初始化语法形式。
构造函数 ( 形参表 ) : 对象成员1(形参表 ) , … , 对象成员n (形参表 ) ;
初始化:
·出现成员对象时,该类的构造函数要包含对象成员的初始化。如果构造函数的成员初始化列表没有对成员对象初始化时,则使用成员对象的无参(缺省)构造函数。
·建立一个类的对象时,要先执行成员对象自己的构造函数,再执行当前类的构造函数。
·成员对象的构造函数调用次序和成员对象在类中的说明次序一致(声明顺序为:a1、b1、b2),与它们在成员初始化列表中出现的次序无关(初始化列表顺序为:b1、b2、a1)。
·析构函数的调用顺序相反
#include<iostream>
using namespace std;
class A
{ public:
A(int x):a(x){ }
int a ;
};
class B
{ public:
B( int x, int y ) : aa(x) { b = y ; }
void out() { cout<<"aa = "<<aa.a<<endl<<"b = "<<b<<endl ; }
private:
int b ;
A aa ;
} ;
int main()
{ B objB( 3, 5 ) ;
objB.out() ;
}
(八)友元函数
1. 如果在本类(类A)以外的其他地方定义了一个函数(函数B),这个函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数,在类体中用friend对其(函数B)进行声明,此函数就称为本类(类A)的友元函数。
·友元函数(函数B)可以访问这个类(类A)中的私有成员。
例1:定义一个boat类,含有weight数据成员。定义该类的一个友元函数, 该函数能输出一个boat对象的重量。
class Boat{
int weight;
public:
Boat(int w):weight(w){}
friend void display_weight(Boat &a)
};
void display_weight(Boat &a){
cout<<a.weight<<endl;}
int main(){
Boat b(100);
display_weight(b);
return 0;
}
例2:将成员函数声明为友元函数。
#include <iostream>
using namespace std;
class Date; //对Date类的提前引用声明
class Time //定义Time类
{
public:
Time(int,int,int);
void display(Date &);
private:
int hour;
int minute;
int sec;
};
class Date //声明Date类
{
public:
Date(int,int,int);
friend void Time∷display(Date &); //声明Time中的display函数为友元成员函数
private:
int month, day, year;
};
Time∷Time(int h,int m,int s) //类Time的构造函数
{
hour=h, minute=m, sec=s;
}
void Time∷display(Date &d){
cout<<d.month<<″/″<<d.day<<″/″<<d.year<<endl;
cout<<hour<<″:″<<minute<<″:″<<sec<<endl;
}
Date∷Date(int m,int d,int y)
{
month=m;
day=d;
year=y;
}
int main( ){
Time t1(10,13,56);
Date d1(12,25,2004);
t1.display(d1);
return 0;
}
2. 友元类
·class B
{
……………
private:
A obj1;
};
A是另一个类,B类的成员函数要访问A的私有成员,此时,需要在A类中,把类B声明为A的友元。
·若F类是A类的友元类,则F类的所有成员函数都是A类的友元函数,友元类通常设计为一种对数据操作或类之间传递消息的辅助类。
friend class 类名;
#include<iostream>
using namespace std ;
class A
{ friend class B ;
public :
void Display()
{ cout << x << endl ; } ;
private :
int x ;
} ;
class B
{ public :
void Set ( int i ) { Aobject . x = i ; }
void Display () { Aobject . Display () ; }
private :
A Aobject ;
} ;
int main()
{ B Bobject ;
Bobject . Set ( 100 ) ;
Bobject . Display () ;
}
(九)运算符重载
1.一元运算符:Object op 或 op Object
·重载为成员函数,解释为:Object . operator op ()。操作数由对象Object通过this指针隐含传递。
·重载为友元函数,解释为:operator op (Object)。操作数由参数表的参数Object提供。
2.二元运算符:ObjectL op ObjectR
·重载为成员函数,解释为:ObjectL . operator op ( ObjectR ),左操作数由ObjectL通过this指针传递,右操作数由参数ObjectR传递
·重载为友元函数,解释为:operator op ( ObjectL, ObjectR ) 左右操作数都由参数传递
3. ·成员运算符函数的原型在类的内部声明格式如下:
class X {
//…
返回类型 operator运算符(形参表);
//…
}
·在类外定义成员运算符函数的格式如下:
返回类型 X::operator运算符(形参表)
{
函数体
}
4. 双目运算符重载为成员函数
·对双目运算符而言,成员运算符函数的形参表中仅有一个参数,它作为运算符的右操作数,此时当前对象作为运算符的左操作数,它是通过this指针隐含地传递给函数的。
#include <iostream.h>
class Complex
{
public:
Complex( ) {real=0,imag=0;}
Complex(double r,double i) {real=r; imag=i;}
Complex operator + (Complex &c2);
void display( );
private:
double real;
double imag;
};
void Complex::display( ){
cout<<"("<<real<<","<<imag<<"i)"<<endl;}
Complex Complex:: operator + (Complex &c2) {
return Complex(real+c2.real, imag+c2.imag);}
int main( ){
Complex c1(3,4),c2(5,-10),c3;
c3=c1+c2;//相当于c3=c1.operator +(c2)
cout<<"c1=";c1.display( );
cout<<"c2=";c2.display( );
cout<<"c1+c2 ="; c3.display( );
return 0;
}
5. 单目运算符重载为成员函数
·对单目运算符而言,成员运算符函数的参数表中没有参数,此时当前对象作为运算符的一个操作数。
有一个Time类,包含数据成员minute(分)和sec(秒),模拟秒表,每次走一秒,满60秒进一分钟,此时秒又从0开始算。要求输出分和秒的值。
class Time
{
public:
Time( ){minute=0;sec=0;}
Time(int m,int s):minute(m),sec(s){ }
Time operator++( ); //声明前置自增运算符“++”重载函数
Time operator++(int); //声明后置自增运算符“++”重载函数
private:
int minute;
int sec;
};
Time Time∷operator++( ) //定义前置自增运算符“++”重载函数
{
if(sec++>=60) {
sec-=60; //满60秒进1分钟
minute ++;
}
return *this; //返回当前对象值
}
Time Time∷operator++(int) //定义后置自增运算符“++”重载函数
{
Time temp(*this);
sec++;
if(sec>=60) {
sec-=60;
++minute;
}
return temp; //返回的是自加前的对象
}
一般而言,采用成员函数重载单目运算符时,以下两种方法是等价的:
@aa; // 隐式调用
aa.operator@(); // 显式调用
成员运算符函数operator @所需的一个操作数由对象aa通过this指针隐含地传递。因此,在它的参数表中没有参数。
6. 用友元函数重载
·在第一个参数需要隐式转换的情形下,使用友元函数重载运算符是正确的选择
·友元函数没有 this 指针,所需操作数都必须在参数表显式声明,很容易实现类型的隐式转换
·C++中不能用友元函数重载的运算符有
= () [] ->
例 复数运算
#include<iostream>
using namespace std;
class Complex
{ public:
Complex( double r =0, double i =0 ) { Real = r ; Image = i ; }
Complex(int a) { Real = a ; Image = 0 ; }
void print() const ;
friend Complex operator+ ( const Complex & c1, const Complex & c2 ) ;
friend Complex operator- ( const Complex & c1, const Complex & c2 ) ;
friend Complex operator- ( const Complex & c ) ;
private:
double Real, Image ;
};
Complex operator + ( const Complex & c1, const Complex & c2 )
{ double r = c1.Real + c2.Real ; double i = c1.Image+c2.Image ;
return Complex ( r, i ) ;
}
Complex operator - ( const Complex & c1, const Complex & c2 )
{ double r = c1.Real - c2.Real ; double i = c1.Image - c2.Image ;
return Complex ( r, i ) ;
}
Complex operator- ( const Complex & c )
{ return Complex ( -c.Real, - c.Image ) ; }
void Complex :: print() const
{ cout << '(' << Real << " , " << Image << ')' << endl ; }
7. 成员运算符函数与友元运算符函数的比较
(1) 成员运算符函数比友元运算符函数少带一个参数(后置的++、--需要增加一个形参)。
(2) 双目运算符一般可以被重载为友元运算符函数或成员运算符函数,但当操作数类型不相同时,必须使用友元函数。
8. 重载赋值运算符
·赋值运算符重载用于对象数据的复制 operator= 必须重载为成员函数
·重载函数原型为:类名 & 类名 :: operator= ( 类名 ) ;
9. 重载运算符[]和()
·运算符 [] 和 () 是二元运算符,[] 和 () 只能用成员函数重载,不能用友元函数重载
(1).重载下标运算符 []
[] 运算符用于访问数据对象的元素
重载格式:类型 类 :: operator[] ( 类型 ) ;
设 x 是类 X 的一个对象,则表达式:x [ y ]
可被解释为:x . operator [ ] ( y )
#include<iostream>
using namespace std;
class vector
{ public :
vector ( int n ) { v = new int [ n ] ; size = n ; }
~ vector ( ) { delete [ ] v ; size = 0 ; }
int & operator [ ] ( int i ) { return v [ i ] ; }
private :
int * v ; int size ;
};
int main ( )
{ vector a ( 5 ) ;
a [ 2 ] = 12 ;
cout << a [ 2 ] << endl ;
}
(2).重载函数调用符 ()
·() 运算符用于函数调用
重载格式 类型 类 :: operator() ( 参数表 ) ;
·设 x 是类 X 的一个对象,则表达式x ( arg1, arg2, … )
可被解释为:x . operator () (arg1, arg2, … )
10. 重载流插入和流提取运算符
·istream 和 ostream 是 C++ 的预定义流类
·cin 是 istream 的对象,cout 是 ostream 的对象
·运算符 << 由ostream 重载为插入操作,用于输出基本类型数据
·运算符 >> 由 istream 重载为提取操作,用于输入基本类型数据
·用友元函数重载 << 和 >> ,输出和输入用户自定义的数据类型
①重载输出运算符“<<”(只能被重载成友元函数,不能重载成成员函数)
定义输出运算符“<<”重载函数的一般格式如下:
ostream& operator<<(ostream& out,class_name& obj)
{
out<<obj.item1;
out<<obj.item2;
.. .
out<<obj.itemn;
return out;
}
②重载输入运算符“>>” (只能被重载成友元函数)
定义输入运算符函数 “>>”重载函数的一般格式如下:
istream& operator>>(istream& in,class_name& obj)
{
in>>obj.item1;
in>>obj.item2;
. . .
in>>obj.itemn;
return in;
}
例 1 设计一个集合类,用无符号整数数组表示集合,重载运算符实现集合的基本运算,以及集合元素的输入、输出。
//setTypeHead.h
#include<iostream>
using namespace std;
//集合类
class setType
{ public:
setType( unsigned e=128 ); //构造函数
setType( const setType & B ); //复制构造函数
~setType(); //析构函数
setType operator+= ( unsigned x ); //重载+=,把元素x并入集合
setType operator= ( setType B ); //重载=,集合变量赋值
setType operator() (unsigned x=0); //重载(),集合置元素x,默认置空
setType operator+ ( setType B ); //重载+,求并集
setType operator* ( setType B ); //重载*,求交集
setType operator- ( setType B ); //重载-,求差集
bool operator<= ( setType B ); //重载<=,判集合蕴含
//重载!运算符,判空集。集合空返回false,否则返回true
bool operator ! ();
//重载<,判元素属于集合
friend bool operator< ( unsigned x, setType A );
//重载>>,输入集合元素
friend istream & operator>> ( istream &input, setType &A );
//重载<<,输出集合的全部元素
friend ostream & operator<< ( ostream &output, setType &A );
private:
unsigned *set; //建立动态数组指针
unsigned n; //数组长度
unsigned e; //全集元素个数
};
//test.cpp
#include"setTypeHead.h"
int main()
{ setType setA, setB, setC; unsigned x;
cout << "Input the elements of setA, 1-128, until input 0 :\n";
cin >> setA; //输入setA的元素
cout << "Input the elements of setB, 1-128, until input 0 :\n";
cin >> setB; //输入setB的元素
cout << "setA = " << setA << endl; //输出setA的元素
cout << "setB = "<<setB << endl; //输出setB的元素
cout << "Input x: "; cin >> x;
setA += x; //把元素x并入setA
cout << "Put " << x << " in setA = " << setA << endl;
setC = setA + setB; //求并集
cout << "setC = setA+setB = " << setC << endl;
setC = setA * setB; //求交集
cout << "setC = setA*setB = " << setC << endl;
setC = setA - setB; //求差集
cout << “setC = setA-setB = ” << setC << endl;
//判断setA是否蕴含于setB
if( setA <= setB ) cout << "setA <= setB\n";
else cout << "not setA <= setB\n";
cout << "Input x: "; cin >> x;
//判断元素x是否属于setA
if( x < setA ) cout << x << " in " << setA << "\n";
else cout << x << " not in " << setA << "\n";
setC = setA + setB + setC; //多个集合变量运算
cout << "setC = setA+setB+setC = " << setC << endl;
setC(); //置setC为空集
cout<<"setC = " << setC << endl;
}
以上就是我这段时间所学习的全部内容了,也正好趁着这次写博客,把知识点都顺了一遍,一些遗忘的知识也都回顾了一下,还有知识点如何运用,如何简化也都复习了一遍。但单是看知识点是远远不够的,还需要我们平常多写代码,多练习,不写知识就会遗忘。还有在写代码的时候注意逻辑结构,先把大体框架梳理好,再向里面补充相关函数等。注意有限元素需要const限定,需要封装起来,有的运算符也需要重载什么的,各种小细节都要注意,在准确的前提下提高编写速度,再看看有什么简单方法可以降低空间、时间的复杂度,这是我们都应该追求的。