结构体作为一种特殊的自定义数据类型,它拥有很多数据类型的特点,比如我们可以定义结构体数组或者定义结构体指针:
struct Student
{
int age;
string name;
char sex;
}*stu1;
int main()
{
Student *stu2;
}
上例中,使用了两种方法定义了结构体指针(stu1和stu2),与其他类型指针变量一样,结构体指针也必须有正确的指向才能够正确使用。结构体指针的使用方法如下:
int main()
{
Student ZhangSan={10,"ZhangSan",'m'};
stu1=&ZhangSan;
cout<<stu1->age<<" "<<stu1->name<<" "<<stu1->sex<<endl;
}
// 输出为:10 ZhangSan m
结构体既然是一种自定义类型,自然也可以申请结构体数组,申请方式与其他类型的数组相同:
int main()
{
// Student stu[3]; // 申请一个长度为3的结构体数组stu
Student *p,stu[]={{10,"ZhangSan",'m'}, // 申请一个结构体数组,长度由初始化决定
{15,"LiSi",'m'},
{20,"WangWu",'w'}};
p=stu; // 由于stu是不可变的地址值,因此这里使用可以自增的p指针进行寻址
for(int i=0;i<3;i++,p++)
{
cout<<p->age<<" "<<p->name<<" "<<p->sex<<endl;
}
}
// 输出为:10 ZhangSan m
// 15 LiSi m
// 20 WangWu w
当然,我们也可以像引用普通数组一样的方式引用结构体数组,如stu[1]。对于二维的结构体数组,也是可以只指定列数并直接在声明时初始化。
结构体可以看做是一种自定义的数据类型,因此也可以作为一种特殊的数据类型出现在其他的结构体里:
struct Student
{
int age;
string name;
char sex;
struct detail
{
string spe_course;
string ele_course;
string opt_course;
}stu;
};
上述方法是在一个结构体内定义另一个结构体,除了这种方式,我们还可以在结构体外进行定义:
struct detail
{
string spe_course;
string ele_course;
string opt_course;
};
struct Student
{
int age;
string name;
char sex;
detail stu;
};
这两种定义方法效果是一样的。当然,嵌套结构体(detail)的结构体(Student)中必须要声明被嵌套的结构体(detail)类型的变量。与类的多层继承与多重继承类似,结构体也可以有多层嵌套和多重嵌套。这样的嵌套使用原理都是相同的,但因为容易造成混乱,应当慎重使用。
嵌套的结构体赋值方法如下:
int main()
{
Student ZhangSan;
ZhangSan.name="ZhangSan";
ZhangSan.stu.opt_course="C++ programming language";
cout<<ZhangSan.name<<" "<<ZhangSan.stu.opt_course<<endl;
}
// 输出为:ZhangSan C++ programming language
当然,我们也可以在声明的时候初始化:
int main()
{
Student LiSi={18,"LiSi",'m',{"python programming language","C++ programming language","Database"}};
cout<<LiSi.name<<" "<<LiSi.stu.opt_course<<endl;
}
// 输出为:LiSi Database
当然,我们可以嵌套一个结构体指针:
struct detail
{
string spe_course;
string ele_course;
string opt_course;
};
struct Student
{
int age;
string name;
char sex;
detail *p;
};
int main()
{
detail stu={"python programming language","C++ programming language","Database"};
Student ZhangSan={18,"LiSi",'m',&stu};
cout<<ZhangSan.name<<" "<<ZhangSan.p->opt_course<<endl;
}
在C++中,我们可以将类型名、类名、自定义结构体名等进行改名。对类型名重命名的方法如下:
typedef string Name;
这样,我们在之后的代码中就可以使用Name或string声明一个字符串类型。这样做的好处在于,我们可以给string类型一个特殊的意义,就像注释一样。另外,我们还可以给名字很复杂的类型名(如函数指针类型):
int add(int a,int b)
{
函数体
}
typedef int(*Pfun)(int,int); // 将函数指针类型int(*)(int,int)重命名为Pfun
由于C++依靠参数列表识别函数,所以我们在给函数指针重命名时,方法与之前略有不同。
对结构体类型进行重命名有两种方式,其一为:
struct member
{
int age;
string name;
char sex;
};
typedef member Student;
其二为:
typedef struct Student
{
int age;
string name;
char sex;
}member;
这种方法中member是一个Student的别名,就不是Student下的一个具体参数了。使用这两种方法定义的Student都有了两个名字,我们试试来使用它:
int main()
{
Student ZhangSan={10,"ZhangSan",'m'};
member LiSi={20,"LiSi",'m'};
cout<<ZhangSan.name<<" "<<LiSi.name<<endl;
}
// 输出为:ZhangSan LiSi
对自定义类的重命名同样有两种方式,重命名方法与结构体完全类似,这里不做赘述,有兴趣的小伙伴可以自己试试。
结构体相关类型的参数在C++中可以作为函数的参数:
struct content
{
double a;
double b;
char oper;
double res;
};
double calculate(struct content x) // 形参也可以只写成content形式
{
switch (x.oper)
{
case '+':
return x.a+x.b;
case '-':
return x.a-x.b;
case '*':
return x.a*x.b;
case '/':
if (x.b==0)
{
x.b=1;
}
return x.a/x.b;
}
return -1;
}
int main()
{
content test={1,2,'+'};
cout<<calculate(test);
}
// 输出为:3
这里的Switch是种偷懒的处理,因为Switch中带有的return可以强行退出函数,所以不需要再额外给出break语句。
当然,结构体指针或者结构体数组也是可以作为函数参数的:
// 结构体的定义同上例
typedef content* cont;
void calculate(cont x) // 形参也可以只写成content形式
{
switch (x->oper)
{
case '+':
x->res=x->a+x->b;
break;
case '-':
x->res=x->a-x->b;
break;
case '*':
x->res=x->a*x->b;
break;
case '/':
if (x->b==0)
{
x->b=1;
}
x->res=x->a/x->b;
break;
default:
x->res=-1;
break;
}
}
int main()
{
content exam={1,2,'/'};
calculate(&exam);
cout<<exam.res<<endl;
}
虽然这两种方法实现的功能类似,但使用指针作为函数的参数,执行效率会比传递普通的形式参数高效很多。在其他场合,合理使用指针也可以提升程序的执行效率。
除了可以作为函数的参数,结构体类型还可以作为类的属性和方法的参数,这里我们不做具体介绍,但后文会在例子里用到。
C++中设计有一种用来避免命名冲突的机制,我们可以用关键字namespace将一组标识符封装在一个命名空间中,以便避免在程序中使用这些标识符时不会与其他部分可能出现的重名内容发生冲突。命名空间可以帮助组织和管理代码,使其更易于理解和维护。定义命名空间的方法为:
namespace 自定义空间名称
{
命名空间内容,可以放自定义类、结构体等
}
举个例子:
namespace TUniversity
{
typedef string StuID;
typedef string AdmissionDate;
typedef string Name;
struct Student
{
StuID id;
AdmissionDate data;
Name name;
};
class academic_information
{
public:
Student a;
string Graduation_Date;
academic_information():a{"00000","00000","ZhangSan"}{}
academic_information(Student x):a{x.id,x.data,x.name},Graduation_Date(to_string(stoi(a.data) + 40000))
{
/*
C++中stoi()函数可以将字符串转换成整数
to_string()函数可以将其他类型数据转换陈字符串类型
*/
}
};
}
这样,我们就定义了一个Tuniversity的自定义命名空间,里面包含了Student结构体,academic_information类以及为string类型起了一些别名。放在命名空间中的内容是无法直接使用的:
int main()
{
Student zhangsan;
}
这样使用就会出现没有在使用范围内定义Student的错误。正确的使用方法为:
int main()
{
TUniversity::academic_information ZhangSan; // 命名空间存储内容的使用
TUniversity::Student lisi={"2401082511","20240123","LiSi"};
TUniversity::academic_information LiSi(lisi);
cout<<ZhangSan.a.data<<" "<<ZhangSan.a.name<<" "<<ZhangSan.a.id<<endl;
cout<<LiSi.a.data<<" "<<LiSi.a.name<<" "<<LiSi.a.id<<" "<<LiSi.Graduation_Date<<endl;
}
// 输出为:00000 ZhangSan 00000
// 20240123 LiSi 2401082511 20280123
命名空间也可以嵌套另一个命名空间,其原理并不复杂,大家可以自行尝试一下。
当然,我们也可以使用这样的方式使用命名空间的内容:
using namespace TUniversity;
这样,之后既可以直接引用TUniversity空间的内容了:
int main()
{
academic_information ZhangSan;
Student lisi={"2401082511","20240123","LiSi"};
academic_information LiSi(lisi);
}
大家对这种引用方式熟悉吗?我在每次写代码之前都会有一句:
using namespace std;
其实这就是在引用std命名空间中的所有内容了,cout、endl、string等等都出自于命名空间std中。当我们引用自己写的.cpp文件时,在保证不会有名称冲突的前提下就可以用这样的引用方式快速地使用该空间内定义的内容。
本节我们介绍了更多的结构体使用方法,重命名方法以及C++中一个很好用的避免重名的机制——命名空间。这两节没有讲到C++与python的对比了,因为python没有结构体等类型和机制。下节我们会介绍C++的抛出异常、捕获异常。