一.结构体
结构体是一种自定义的类型,使用这种自定义类型可以描述一些复杂对象。前面学到的都是单一的数据类型,比如:char short int double类型,可以描述某一个变量或生活中的东西,但是在现实生活中,总会出现一些复杂的类型,比如:人、书、成绩等。这种对象有着很多的分支类型,使用单一的数据类型是不能描述复杂的对象的。C++引入了结构体和类解决这个问题。在C++语言中,结构体和类是相似的,这里首先介绍结构体。
1.结构体类型的声明和变量的定义
结构体类型声明的关键字是struct,结构体类型声明的基本语法如下:
cpp
struct tag
{
成员变量列表; //成员变量可以有1个,也可以有多个
成员函数列表; //成员函数可以有,也可以没有
} 结构体变量列表; //在声明结构体类型的同时,创建结构体变量,可以有多个,中间使⽤逗号隔开
上述结构体的名字是tag,结构体里面可以包含成员变量和成员函数。在创建结构体变量时,变量名可以写在大括号后面。具体的内容与所要描述的对象有关,下面请看结构体声明的相关例子:
cpp
struct Stu
{
string name;
int chinese;
int math;
int total;
} s1, s[50];
上述结构体为学生结构体,内部包含成员变量,没有成员函数。成员变量有姓名,语文成绩,数学成绩,总成绩。并且在声明完结构体之后,创建了两个结构体变量,其中一个是结构体数组。里面可以存放50个学生的信息。
cpp
struct Stu
{
string name;
int chinese;
int math;
int total;
}; //结构体类型
struct Stu s1;
Stu s1; //可以省略struct
Stu s[50];
在创建变量的时候,结构体类型的struct就可以省略掉了,但是在C语言中是不可以省略的;在创建结构体变量的时候,结构体变量的名字不能和结构体类型的名字相同;声明结构体是不占用内存的,当使用结构体创建了变量之后,才会向内存申请空间;结构体变量可以是全局变量,也可以是局部变量。
2.结构体变量的特点
(1)结构体的初始化
结构体的初始化和数组的初始化类似,需要使用到大括号,将初始化的内容数据按照顺序放在大括号内部即可。下面是结构体初始化的例子:
cpp
struct Stu
{
string name;
int chinese;
int math;
int total;
};
struct Stu s1 = {"zhangsan", 85, 95, 180};
上述代码首先声明了结构体类型,用于存储学生的信息。成员变量有姓名、语文成绩、数学成绩、总成绩。接下来创建了结构体的变量,姓名为zhangsan,语文成绩是85,数学成绩是95,总成绩是100。

上面是两个结构体变量的初始化信息。每个结构体变量都会有相应的成员变量储存相关的值。这样更方便管理该结构体的相关数据。
(2)结构体整体操作
结构体变量中虽然包含多个成员,而且成员可能是不同类型的。但是一个结构体变量可以看作一个整体,是可以直接进行赋值操作的。下面是结构体整体赋值操作的代码演示:
cpp
struct Stu s1 = {"zhangsan", 85, 95, 180};
struct Stu s2;
s2 = s1;//整体赋值,这样s2的内容就会和s1的⼀样
上面的结构体变量s1初始化之后,通过互相赋值。对s2结构体进行了整体赋值。
(3)结构体成员访问
结构体成员访问的基本形式是:结构体变量.成员名。这里的点是结构体成员访问操作符,可以通过这个操作符获取结构体内部的成员信息。
因为每个结构体变量中都有属于自己的成员,所有必须使用点.这种结构体成员访问操作符。通过这种方式找到成员后,可以直接给结构体变量的成员输入值,或直接存储和使用。
cpp
#include <iostream>
using namespace std;
struct Stu
{
string name;
int chinese;
int math;
int total;
};
int main()
{
struct Stu s1;
s1.name = "lisi"; //直接赋值
s1.chinese = 95;
cin >> s1.math; //输⼊
s1.total = s1.chinese + s1.math; //结构体成员直接参与运算
cout << s1.total << endl; //输出操作
return 0;
}
上述代码首先声明了学生结构体,下面主函数中进行了结构体变量的创建和初始化。通过C++语言的输入函数对结构体的某一成员变量进行赋值。
(4)结构体的嵌套
当然如果结构体中嵌套了其他结构体成员,这里初始化的时候,也可以大括号中嵌套大括号,访问嵌套成员中成员可以连续使用点操作符。例如:
cpp
struct Score
{
int chinese;
int math;
int engish;
};
struct Stu
{
string name;
struct Score score;
int total;
int avg;
};
int main()
{
struct Stu s = {"wangwu", {80, 90, 100}, 0, 0}; //初始化
s.total = s.score.chinese + s.score.engish + s.score.math; //嵌套成员的访问
s.avg = s.total / 3;
cout << s.total << endl;
cout << s.avg << endl;
return 0;
}
在上述学生结构体中嵌套了成绩结构体,在学生结构体进行初始化时,可以在大括号内部嵌套大括号从而对成绩结构体进行成员变量的初始化。
3.结构体的成员函数
以上我们讲的是结构体的基本知识,C语言中的结构体就是上面那样,但是C++中结构体和C语言结构体的有一个比较大的差异就是:C++中的结构体中除了有成员变量之外,还可以包含成员函数。
- C++的结构体会有一些默认的成员函数,比如:构造函数、析构函数等,是编译器默认生成的,如果觉得不适合,当然可以自己定义这些函数,这些函数都是自动被调用,不需要手动调用。
- 除了默认的成员函数之外,我们可以自己定义一些成员函数,这些成员函数可以有,也可以没有,完全根据的实际的需要来添加就可以。
- 这些成员函数可以直接访问成员变量。
- 成员函数的调用也可以使用点操作符。
(1)代码举例
cpp
//代码1-演⽰⾃定义成员函数
#include <iostream>
using namespace std;
struct Stu
{
//成员变量
string name;
int chinese;
int math;
int total;
void init_stu() //初始化
{
name = "⼩明";
chinese = 99;
math = 95;
total = math + chinese;
}
void print_stu() //打印
{
cout << "名字:" << name << endl;
cout << "语⽂:" << chinese << endl;
cout << "数学:" << math << endl;
cout << "总分:" << total << endl;
}
};
int main()
{
struct Stu s;
s.init_stu();
s.print_stu();
return 0;
}
上述代码学生结构体里不仅包含了成员变量,也包含了成员函数,各个成员函数有着各自的功能。
(2)构造函数和析构函数
构造函数:构造函数是结构中默认的成员函数之一,构造函数的主要任务是初始化结构体变量。写了构造函数,就不需要再写其他成员函数来初始化结构体成员,而且构造函数是在结构变量创建的时候,编译器自动被调用。构造函数的特征如下:函数名与结构体名相同;没有返回值;构造函数可以重载;若未显示定义构造函数,系统会自动生成默认的构造函数。
析构函数:析构函数是用来完成结构体变量中资源的清理工作,也是结构体中默认的成员函数之一。析构函数在结构体变量销毁的时候,被自动调用。析构函数的特征:析构函数名是在结构体名前加上字符~;无参数没有返回值类型,一个类只能有一个析构函数。若没有显示定义析构函数,系统会自动生成默认的析构函数。这里需要注意的是析构函数不能重载。
(3)代码举例
cpp
//代码2-演⽰构造和析构函数
#include <iostream>
using namespace std;
struct Stu
{
//成员变量
string name;
int chinese;
int math;
int total;
//构造函数
Stu()
{
cout << "调⽤构造函数" << endl;
name = "⼩明";
chinese = 99;
math = 95;
total = math + chinese;
}
//析构函数
~Stu()
{
cout << "调⽤析构函数" << endl;
//对于当前这个类的对象没有啥资源清理
}
void print_stu() //打印
{
cout << "名字:" << name << endl;
cout << "语⽂:" << chinese << endl;
cout << "数学:" << math << endl;
cout << "总分:" << total << endl;
}
};
int main()
{ struct Stu s;
s.print_stu();
return 0;
}
成员函数和自定义函数都是一样的,是可以根据实际需要完成编写。
4.运算符重载
在上一个代码的例子中,在打印结构体成员信息的时候,我们是通过s.print_stu()的方式完成的。但是在C++打印数据时,习惯使用cout函数进行打印数据。比如:
cpp
int n = 100;
float f = 3.14f;
cout << n << endl;
cout << f << endl;
针对于struct Stu类型的变量能不能直接使用cout打印呢?例如如下代码:
cpp
#include <iostream>
using namespace std;
struct Stu
{
//成员变量
string name;
int chinese;
int math;
int total;
};
int main()
{
struct Stu s = {"张三", 90, 80, 170};
cout << s << endl;
return 0;
}

在C++语言中要针对自定义类型的变量,想使用cout和os来输出变量的内容,就对这个输出运算符进行重载,具体代码如下:
cpp
#include <iostream>
using namespace std;
struct Stu
{
//成员变量
string name;
int chinese;
int math;
int total;
};
//重载输出运算符
ostream& operator<<(ostream& os, const Stu & s)
{
os << "名字: " << s.name << endl;
os << "语⽂: " << s.chinese << endl;
os << "数学: " << s.math << endl;
os << "总分: " << s.total << endl;
return os;
}
int main()
{
struct Stu s = {"张三", 90, 80, 170};
cout << s << endl;
return 0;
}
上述代码是运算符重载的例子,运算符的重载会让cout可以直接打印出结构体的变量。
5.结构体排序-sort
说到排序,之前讲过冒泡排序,我们也可以写一个冒泡排序的函数来排序一组结构体数据,但是这里要介绍的是C++语言的STL中的库函数sort,可以直接用来排序数据。今后只要涉及到数据的排序,有没有明确要求自己实现排序算法时,就可以使用sort函数。
(1)函数介绍
cpp
//版本1
template <class RandomAccessIterator>
void sort (RandomAccessIterator first, RandomAccessIterator last);
//void sort(开始位置,结束位置);
//first:指向要排序范围的第⼀个元素的迭代器或者指针。
//last:指向要排序范围的最后⼀个元素之后位置的迭代器或者指针。
//版本2
template <class RandomAccessIterator, class Compare>
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
//void sort(开始位置,结束位置,⾃定义排序函数);
//first:指向要排序范围的第⼀个元素的迭代器或者指针。
//last:指向要排序范围的最后⼀个元素之后位置的迭代器或者指针。
//comp:是⼀个⽐较函数或者函数对象
//这⾥开始位置和结束位置,可以是指针,也可以是迭代器
//⾃定义排序函数 可以是函数,也可以是仿函数
上面两种不同的排序方式,第一种是按照升序排列的;第二种是可以自定义排序规则的。
在默认情况下sort函数按照升序对给定的范围中的元素进行排序。

(2)排序内置类型数据
对数组进行排序:
cpp
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
int arr[] = { 4,5,6,9,7,1,2,8,5,4,2 };
int size = sizeof(arr) / sizeof(arr[0]);
//起始位置和结束位置传的是地址
sort(arr, arr + size);
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}

默认排序的结果是升序。
对字符串的字符进行排序,这里是对字符串中字符的顺序进行排序,按照字符的ASCII码值进行排序的。
cpp
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
string s("defxxxabccba");
sort(s.begin(), s.end());
cout << s << endl;
return 0;
}

(3)自定义排序
sort函数在默认的情况下是按照升序排序,如何按照降序呢?
如果是结构体类型进行排序又该怎么办呢?
这时就要使用自定义排序方式。
cpp
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
sort函数的第三个参数是一个可选的自定义比较函数(或函数对象),用于指定排序的规则。如果不提供这个参数,std::sort默认会使用小于运算符来比较元素,并按照升序排序。这个比较函数,接收两个参数,并返回一个bool类型的值。如果第一个参数应该排在第二个参数之前,则返回true;否则返回false。
comp表示可以自定义一个排序方法,使用方法如下:
<1>创建比较函数
cpp
#include <iostream>
#include <algorithm>
using namespace std;
//
//⾃定义⼀个⽐较函数,这个⽐较函数能够⽐较被排序数据的2个元素⼤⼩
//函数返回bool类型的值
//
bool compare(int x, int y)
{
return x > y;//排降序
}
int main()
{
int arr[] = { 4,5,6,9,7,1,2,8,5,4,2 };
int size = sizeof(arr) / sizeof(arr[0]);
//将函数名作为第三个参数传⼊sort函数中
sort(arr, arr + size, compare);
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
利用自定义的函数写出排序的规则,这样就能按照这个规则进行排序。实现排序自定义。
<2>结构体中重载()运算符-仿函数
cpp
#include <iostream>
#include<algorithm>
using namespace std;
//仿函数⽅式 - 仿函数也叫函数对象
//关于仿函数、操作符重载,要深⼊学习C++,竞赛中涉及的较少、也⽐较浅,会⽤就⾏
struct Cmp
{
bool operator()(int x, int y)
{
return x > y;//排降序
}
}cmp;
int main()
{
int arr[] = { 4,5,6,9,7,1,2,8,5,4,2 };
int size = sizeof(arr) / sizeof(arr[0]);
//将结构体对象作为第三个参数传⼊sort函数中
sort(arr, arr + size, cmp);
for (int i = 0; i < size; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}

(4)排序结构体数据
两个结构体数据也是不能直接比较大小的,在使用sort函数排序的时候,也是需要提供自定义的比较方法。比如:
cpp
#include <iostream>
#include <algorithm>
using namespace std;
struct S
{
string name;
int age;
};
bool cmp_s_by_age(const struct S& s1, const struct S& s2)
{
return s1.age > s2.age; //按年龄降序
}
bool cmp_s_by_name(const struct S& s1, const struct S& s2)
{
return s1.name > s2.name;//按名字降序
}
//测试⾃定义⽐较函数
void test1()
{
struct S s[3] = { {"zhangsan", 20}, {"lisi", 25}, {"wangwu", 18} };
sort(s, s + 3, cmp_s_by_age);
int i = 0;
for (i = 0; i < 3; i++)
{
cout << s[i].name << " " << s[i].age << endl;
}
}
struct CmpByNameLess
{
bool operator()(const struct S& s1, const struct S& s2)
{
return s1.name < s2.name; //按照名字升序
}
};
struct CmpByAgeGreater
{
bool operator()(const struct S& s1, const struct S& s2)
{
return s1.age > s2.age; //按照年龄降序
}
};
//测试结构中重载()运算符实现⽐较
void test2()
{
struct S s[3] = { {"zhangsan", 20}, {"lisi", 25}, {"wangwu", 18} };
sort(s, s + 3, CmpByNameLess);
int i = 0;
for (i = 0; i < 3; i++)
{
cout << s[i].name << " " << s[i].age << endl;
}
}
int main()
{
test1();
test2();
return 0;
}
二.类的学习
C++语言为了更好实现面向对象,更喜欢使用class来替换struct。我们可以将class和struct理解为一回事,但是本质上还是有区别的。接下来学习一下类:
1.类的定义
类的关键字是 class ,类中主要也是由两部分组成,分别为成员变量和成员函数。 形式如下:
cpp
class tag
{
public:
成员变量列表;
成员函数列表;
}; // ⼀定要注意后⾯的分号
class是用来定义类类型的关键字,在类中可以定义成员函数和成员变量。public是类成员权限访问限定符,标志类中的成员可以公开访问及调用。
为什么结构体没有public?
原因:结构体的成员变量和成员函数,默认就是公开的,而class中的成员变量和成员函数默认是私有的。
包含成员变量的类,例如描述一个学生:
cpp
class Stu
{
public:
string name; //名字
int chinese; //语⽂成绩
int math; //数学成绩
int total; //总成绩
};
包含成员变量和成员函数的类,如:
cpp
class Stu
{
public:
void init_stu()
{
name= "⼩明";
chinese = 0;
math = 0;
total = 0;
}
string name; //名字
int chinese; //语⽂成绩
int math; //数学成绩
int total; //总成绩
};
在类中,成员变量和成员函数都是没有数量限制的,即可以有多个成员变量或成员函数。
2.类的使用
(1)创建类对象
cpp
int main()
{
Stu s1; //创建类对象s1
Stu s2; //创建类对象s2
return 0;
}
(2)调用类对象成员
像结构体一样,通过点操作符,既可对类对象的成员进行访问。
cpp
#include <iostream>
using namespace std;
int main()
{
//创建类对象
Stu s1;
//调⽤成员变量
s1.name = "张三";
s1.chinese = 90;
s1.math = 98;
s1.total = s1.chinese + s1.math;
Stu s2;
//调⽤成员函数
s2.init_stu();
return 0;
}
3.访问权限控制

访问限定符是C++语言的关键字,用于指定类成员的访问权限。
访问限定符主要有三个:
- public:成员被声明为public后,可以被该类的任何方法访问,包括在类的外部。
- protected:成员被声明为protected后,可以被该类访问。
- private:成员被声明为private后,只能被该类的成员函数访问。
cpp
#include <iostream>
using namespace std;
class Stu
{
public: //将成员函数设置为公有属性
void init_stu()
{
name= "⼩明";
chinese = 0;
math = 0;
total = 0;
}
private: //将成员变量设置为私有属性
string name; //名字
int chinese; //语⽂成绩
int math; //数学成绩
int total; //总成绩
};
int main()
{
Stu stu;
stu.init_stu(); //访问公有成员函数
cout << stu.name << endl; //访问私有成员变量,编译器报错
return 0;
}
通过运行结果可以看出:类外可直接访问设置为共有的成员,而不可以直接访问私有的成员。

习惯上,外部可访问的成员函数通常设置为公有属性,而为了提高成员变量的访问安全性上,通常将成员变量设置为私有属性,即只有类内部可以访问。
4.结构体和类的区别
C++语言中struct和class的区别:
C++语言兼容了C语言,所以C++中struct既可以当成结构体使用,还可以定义类,和class定义类是一样的。区别是struct定义的类默认访问权限是public,class定义的类默认访问权限是private。
结论:类的定义既可以使用struct,也可以使用class。C++语言更常用class关键词来定义类,因此在选用class的时候需要加上访问限定符public才可以在类外调用类的成员函数和成员变量。