C++数据类型:
(其中结构体和枚举是由多种成员构成的复合类型)
在存储和处理大批量数据时,一般会使用数组来实现,但是每一个数据的类型及含义必须一样。而把不同类型、不同含义的数据当作一个整体来处理,C++ 提供了结构体(struct)来解决这类问题。
一. 结构体
1、结构体定义
C++ 中的结构体是由一系列具有相同类型或不同类型的数据构成的数据集合,也叫结构。
使用结构体,必须要先声明一个结构体类型,再定义和使用结构体变量。结构体类型的声明格式如下:
struct 类型名{
数据类型1 成员名1;
数据类型2 成员名2;
…
};
2、定义结构体变量
定义结构体变量格式如下:
struct 结构体类型名 变量名列表;
也可以把结构体类型声明和变量定义合在一起,格式如下:
struct 类型名{
数据类型1 成员名1;
数据类型2 成员名2;
…
}; //末尾符号不要忘记
3、结构体的使用
结构体变量具有以下特点:
(1)可以对结构体变量的整体进行操作。
例如:swap(a[i],a[j])
(2)可以对结构体变量的成员进行操作。
引用结构体变量中成员的格式为:
结构体变量名. 成员名
(3)结构体变量的初始化方法与数组类似。
例:学生信息
【问题描述】
输入一个学生的信息,包括姓名、性别、年龄、体重,再输出这些信息。
【输入格式】
一行,依次是学生的姓名、性别、年龄、体重。
【输出格式】
一行,依次是姓名、性别、年龄、体重(体重保留一位小数)。
【输入样例】
zhangsan m 20 90.5
【输出样例】
zhangsan m 20 90.5
#include<bits/stdc++.h>
using namespace std;
struct student{
string name;
char sex;
int age;
double weight;
};
int main(){
student stu;
cin >> stu.name >> stu.sex >> stu.age >> stu.weight;
cout << stu.name << “ “ << stu.sex << “ “ << stu.age << “ “ ;
cout << fixed << setprecision(1) << stu.weight << endl;
return 0;
}
4、运算符重载
“运算符重载”常用于解决结构体或自定义数据类型的加法、减法等特殊含义的运算。运算符重载的一般格式为:
类型名 operator 运算符 (const 类型名 变量)const{
…
}
例:
【问题描述】
为了了解学生的课后作业负担情况,需要统计学生连续若干天完成作业所需的总时间。现在,输入某位学生 n 天完成作业的时间,格式为时、分、秒,最后输出这位学生 n 天完成作业的总时间(秒)。
【输入格式】
第 1 行一个正整数 n,表示有 n 天;
第 2~ 第 n+1 行,每行 3 个整数,分别代表时、分、秒。
【输出格式】
一行信息,表示这个学生完成作业的总时间,具体格式见输出样例。
【输入样例】
3
1 20 30
1 20 45
1 19 30
【输出样例】
4hour 0minute 45second
【问题分析】
本题需要把若干时间(时、分、秒)相加,但又不是普通的加法运算。所以,可以利用运算符重载,另外定义一个“+”运算,实现对两个结构体变量的加法。
#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;
}
二、标准库类型string
1、定义
(1)string 表示可变长度的字符序列
- 字符串是对象
(2)string 类支持字符串对象的各种操作
- 各种初始化方式
- 字符串之间的复制、比较、连接
- 查询字符串长度和判断字符串是否为空
- 访问字符串中的单个字符
(3)使用string 类要包含头文件<string>
2、简单使用
#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] <<" ";
}
string的常用操作
3、定义和初始化string对象
4、读写string对象
(1)可以用循环读取未知数量的string对象
输入操作符返回输入流对象,如果输入流对象处于有效状态,表示没有遇到文件结束或非法输入
(2)getline()函数
- 两个参数:输入流对象和存放读入字符串的string对象
- 从指定输入流中读取内容,遇到换行符为止;
- 将所读内容存入指定的string对象中,流中的换行符被读取并丢弃
- 返回参数输入流对象
5、常用函数
(1)判空函数
empty()函数判断string对象是否为空,返回一个布尔值
stringObj.empty()
//每次读取一行文本,输出非空的行
string line;
while(getline(cin, line))
if(!line.empty()) //line不为空,输出
cout << line << endl;
(2)获取string对象长度
size()函数返回string对象的长度即对象中字符的个数
返回的长度是string::size_type类型
//每次读取一行文本,输出长度超过80个字符的行
string line;
while(getline(cin, line))
if(line.size() > 80)
//字符串长度的类型
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 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; //错误
注: string对象和C风格字符串
- 字符串字面值不是string类型,而是const char*类型
- string对象和C风格字符串的转换
(1)可以将C风格的字符串直接赋给string对象,反之不可
(2)用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 C++向C的转换
三、指针和引用(与对象地址相关的复合类型)
1、指针
(1)指针的特点
- 指针持有一个对象的地址,称为指针“指向”该对象
- 通过指针可以间接操纵它指向的对象
(2)定义指针变量的语法
- 每个指针都有相关的类型,要在定义指针时指出
类型 *指针变量;
int *pi;
int* pi;
char *pc1, *pc2;
char* pc1, pc2;
char *pc1, pc2;
char* pc1, *pc2;
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
3、指向对象的指针
指向一个对象的指针有两个存储单元与之相关
- 一个是指针自己的存储单元,里面存放着所指对象的地址;
- 另一个就是指针指向的对象的存储单元,里面存放该对象的值。
可以定义存放指针对象的地址的指针
int ival = 1024;
int *pi = &ival;
int **ppi = π //ppi是指针的指针,存放pi的地址
4、指针的类型
(1)
- 指针的类型即指针指向的类型
- 每个指针都有相关的类型,需要在定义指针时指出
type* pointer
- 指针的类型指出了如何解释该内存地址保存的内容,以及该内存区域应该有多大
- 不同类型指针的表示方法和保存的地址值并没有分别,区别只是指针指向的对象类型不同
(2)空指针 - 指针值为0时是一个空指针,即不指向任何对象的指针
- 表示空指针的2种方法
0和预处理常量NULL
<cstdlib>
// 生成空指针的2种方法
int *p1 = 0;
int *p2 = NULL;
//不能写成下面的样子:
int zero = 0;
int *p4 = zero;
(3)通用指针 void*指针
- 可以持有任何类型的地址值,即通用指针
- 相关的值是个地址,但是该地址保存的对象类型不知道
- 不能操纵void指针指向的对象,只能传送该地址值或者和其他地址值进行比较
- 不允许void指针到其他类型指针的直接赋值
5、指针的典型用法包括
- 构建链式的数据结构,如链表和树;
- 管理程序运行时动态分配的对象;
- 作为函数的参数。
6、存储空间分配策略
静态(编译时)分配空间
编译器在处理程序源代码时分配内存;
效率高,灵活性差,运行前就要知道程序需要的内存大小和类型
动态(运行时)分配空间
程序运行时调用运行时刻库函数来分配内存;
占用程序运行时间,更灵活
静态和动态内存分配在语法上的主要区别
- 静态对象是有名字的变量,可以直接对其进行操作;动态对象没有名字,要通过指针间接地对它进行操作。
- 静态对象的空间分配与释放由编译器自动处理,动态对象的空间分配与释放必须由程序员显式地管理。
程序使用动态内存的原因
- 程序不知道自己需要使用多少对象
- 程序不知道所需对象的准确类型
- 程序需要在多个对象间共享数据
动态存储空间管理
堆(heap)、自由存储区、动态存储区
系统为所有程序提供了一个运行时可用的内存池,这个内存池被称为程序的自由存储区或堆
动态内存管理方法
C++通过new和delete运算符进行动态存储空间的管理
动态内存管理方法
new
delete
7、new运算符
new表达式的三种形式
- 分配单个对象:new 类型 或者 new 类型(初始值)
- 分配多个连续存储的对象: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>
#include <new>
char* buf = new char [1000];
//预分配一段空间,首地址在buf中保存
int main(){
int* pi = new (buf) int;
//在buf中创建一个int对象,此时不再重新从堆上分配空间
}
new运算符分配的空间用delete运算符释放
- 释放new分配的单个对象的delete形式
delete 指针; - 释放new分配的数组的delete形式
delete[] 指针; - 定位new没有对应的delete表达式
8、引用
类型 &引用名 = 初始值;
- 引用又称为别名,它可以作为对象的另一个名字;
- 通过引用可以间接地操纵对象;
- 程序中,引用主要用作函数的参数。
- 引用必须被初始化,初始值是一个有内存地址的(引用一旦初始化,就不能再指向其他的对象,对引用的所有操作都会被应用在它所指向的对象上)
- 定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用
- 一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,所以引用必须初始化
int x = 100, y = 20;
int &r = x; // r是x的引用
r = y; // r不会变成y的引用,而是x = y
9、const限定指针
- 指向const对象的指针(非const )
const type *cp; 或者type const *cp;
cp是指向常量的指针,它所指向的内存中的内容不可以改变,即*cp的值不能改变 - 指向非const对象的const指针
type* const cp = initAddressValue;
cp是常量指针,初始化后值不能改变,指向固定的单元 - const限定的左值引用不可修改
const引用可以绑定到const对象
不能用非const引用指向const对象