【黑马程序员匠心之作|C++教程】C++基础入门、通讯录管理系统
文章目录
- C++基础入门
- 1 C++初识
- 1.2 注释
- 1.3变量
- 1.4 常量
- 1.5 关键字
- 1.6 标识符命名规则
- 2 数据类型
- 2.1 整形
- 2.2 `sizeof` 关键字
- 2.3 实型(浮点型)
- 2.4 字符型
- 2.5 转义字符
- 2.6 字符串类型
- 2.7 布尔类型 bool
- 2.8 数据的输入
- 3 运算符
- 4 程序流程结构
- 4.1 选择结构
- 4.2 循环结构
- 4.3 跳转语句
- 5 数组
- 5.2 一维数组
- 5.3 二维数组
- 6 函数
- 6.2 函数的定义与调用
- 6.4 值传递
- 6.5 函数的常见样式
- 6.6 函数的声明
- 6.7 函数的分文件编写
- 7 指针
- 7.2 指针变量的定义和使用
- 7.3 指针所占的内存空间
- 7.4 空指针和野指针
- 7.5 const 修饰指针
- 7.6 指针和数组
- 7.7 指针和函数
- 7.8 指针、数组、函数
- 值传递的例外,数组可以通过值传递修改 实参数组元素的值
- 8 结构体
- 8.2 结构体定义和使用
- 8.3 结构体数组
- 8.4 结构体指针
- 8.4.1 结构体的所占内存空间——内存字节对齐
- 8.5 结构体嵌套结构体
- 8.6 结构体做函数参数
- 8.7 结构体中 const使用场景
- 8.8 结构体案例
- 实战:通讯录管理系统
- 主要功能
- 1. 整体框架搭建
- 2. 具体功能实现
- 2.1 添加联系人
- 2.2 显示联系人
- 2.3 删除联系人
- 2.4 查找联系人
- 2.5 修改联系人
- 2.6 清空通讯录的所有信息
C++基础入门
1 C++初识
1.2 注释
C++编译过程:预处理,编译,汇编,链接
在预处理期,会把.cpp
文件中的注释信息去掉,后期不再执行
- 单行注释
// 描述信息
- 多行注释
/* 描述信息 */
1.3变量
作用:给一段指定内存空间起名,通过变量名,可以引用这段内存空间,并对其读取和写入等操作。
不用记内存也不用记内存编号(地址值)
语法:数据类型 变量名 = 变量初始值;
举例:int a = 10;
1.4 常量
作用:用于记录程序中不可更改的数据,例如 一周天数 一年的月份等 是定值 修改则失去原有含义
C++定义常量的两种方式:
- 宏常量
#define 常量名 常量值
- 通常在文件上方定义,表示一个常量
const
关键字 修饰的变量const 数据类型 常量名 = 常量值
- 通常在变量定义前加上关键字const,修饰该变量为常量,不可修改
举例:
#include <iostream>
using namespace std;
#define Week 7 //#define 宏常量,定义在文件上方
int main(){const int month = 12;month = 13;//报错Week = 8;//报错return 0;
}
区别:
#define
是预处理器指令,在预处理期间进行简单的文本替换,意味着其定义的常量没有在内存中分配空间const
是编译器关键字,在编译期间进行处理(包含类型检查以及在常量区分配内存),这些常量在程序期间是只读的,值在编译时确定,且不能被修改
1.5 关键字
作用:关键字是C++中预先保留的单词(标识符),定义变量/常量名字的时候不能与C++关键字冲突,产生歧义
1.6 标识符命名规则
作用:C++规定给标识符(变量、常量)命名时,有一套自己的规则
- 标识符 不能是关键字
- 标识符 只能由字母、数字、下划线组成
- 第一个字符 必须是字母或者下划线
- 标识符中 字母区分大小写
2 数据类型
创建变量的语法:数据类型 变量 = 初始值;
数据类型存在的意义:为 变量 分配相应类型大小的内存空间。
2.1 整形
以下类型 都可以表示整形,区别是所占用空间不同,取值范围不同。
数据类型 | 占用空间 | 取值范围 |
---|---|---|
short 短整型 | 2 字节 16bit | -215 ~ 215-1 |
int 整形 | 4 字节 32bit | -231 ~ 231-1 |
long 长整型 | Windows 4字节,Linux4字节(32位),8字节(64位) |
-231 ~ 231-1 4字节 -263 ~ 263-1 8字节 |
long long 长长整形 | 8字节 64bit | -263 ~ 263-1 |
2.2 sizeof
关键字
作用:统计数据类型所占的内存大小(字节数大小)
语法:sizeof(数据类型)
或 sizeof(变量)
#include <iostream>
using namespace std;
int main(){short num1 = 10;cout << "short : "<< sizeof(short) << endl; //2cout << "short : "<< sizeof(num1) << endl; //2int num2 = 10;cout << "int : "<< sizeof(num2) << endl; //4return 0;
}
// 所占字节大小: short < int <= long <= long long
2.3 实型(浮点型)
作用:表示小数
3.14
三位有效数字,包含小数点前和小数点后的长度
在浮点数表示中,有效数字是指能够精确表示的数字位数,包括小数点前和小数点后的位数。
对于 float 类型:
它使用32位(4字节)来表示浮点数,其中23位用于表示尾数,所以它可以提供大约6-7位的有效数字。
对于 double 类型:
它使用64位(8字节)来表示浮点数,其中52位用于表示尾数,所以它可以提供大约15-16位的有效数字。
数据类型 | 占用空间 | 有效数字范围 |
---|---|---|
float 单精度 | 4 字节 32bit | 7位有效数字 |
double 双精度 | 8 字节 61bit | 15 ~ 16位有效数字 |
float f1 = 3.14; //默认情况下,编译器会把小数变成double类型 如果用float接收 则再转换成float类型
float f1 = 3.14f; // 后面加上'f' 默认单精度float f2 = 3.2455266f; //浮点数类型 会在输出时显示小数点后六位小数
double d2 = 3.2455266;
cout << f2 << endl;
cout << d2 << end;cout << "float : " << sizeof(float) << endl; // 4
cout << "double : " << sizeof(double) << endl; // 8// 小数 的 科学计数法
float f3 = 3e-2; //3*(0.1^2) 0.03
2.4 字符型
作用:表示单个字符
语法:char ch = 'a';
单引号,且单引号中只能有一个字符
- C 和 C++ 中字符型变量只占用 1 个字节
- 字符型变量并不是把字符本身放到内存中进行存储,而是将对应的ASCII码放到存储单元
十进制表示的ASCII码,计算机底层会转换成二进制 0 1:
ASCII码大概由两部分组成:
- 非打印 控制字符:数字 0-31 分配给了控制字符,用于控制像打印机等一些外围设备
- 打印字符:数字 32- 126 分配给了能在键盘上找到的字符,当查看或者打印文档的时候会出现
char ch = 'a';
cout << "char (sizeof): " << sizeof(ch) << endl; //1// 字符型变量 常见错误
// char ch2 = "a"; //报错,创建字符型变量,要用单引号
// char ch2 = 'adf'; //报错,创建字符型变量,单引号内只能用一个字符cout << (int) ch << endl; //97 将ASCII二进制数 强制转换为 十进制数 输出
// 'a' 97, 'A' 65
2.5 转义字符
作用:表示一些不能显示出来的ASCII字符
// cout << '\' << endl; //报错
cout << "\\" << endl; // 输出反斜杠 \
cout << "hello\nworld" << endl; // 中间换行
2.6 字符串类型
作用:表示一串字符
C 风格字符串
char 变量名[] = "字符串值"
示例:
char str1[] = "Hello World"; //需要中括号 cout << str1 << endl;
C++ 风格字符串
string 变量名 = "字符串"
示例:
#include <string> //需要包含头文件 string str1 = "Hello World"; cout << str1 << endl;
2.7 布尔类型 bool
作用:代表真或者假 true(本质1) false(本质 0)
只占用1个字节大小
bool flag = true;
cout << flag << endl; // 1 表示true
cout << "bool (sizeof) : " << sizeof(flag) << endl; //"bool (sizeof):1 布尔类型只占用1个字节
2.8 数据的输入
作用:从键盘获取数据
关键字:cin
语法: cin >> 数据
cin
和cout
对象通过重载各个数据类型的输入和输出运算符来实现对不同数据类型的输入和输出
cin
是用于从标准输入设备(通常是键盘)读取数据的对象。它通过重载了输入运算符 >>
来支持不同数据类型的输入。例如,可以使用 cin >> num;
从标准输入读取一个整数并将其存储到变量 num
中。
cout
是用于向标准输出设备(通常是屏幕)输出数据的对象。它通过重载了输出运算符 <<
来支持不同数据类型的输出。例如,可以使用 cout << "Hello, World!";
将字符串 “Hello, World!” 输出到标准输出。
3 运算符
作用:用于执行代码的运算
- 算数运算符:处理四则运算
取模 % :
两个数相除,除数不可以位0 所以也做不了取模运算
int a = 10, b = 0; // cout << a % b << endl; //报错
两个小数,不可以做取模运算
double d1 = 3.14, d2 = 1.1; //cout << d1 % d2 << endl; //报错
只有整形变量才可以进行取模运算
前置和后置:
本质上是让变量+1,但是区别:
前置递增:先让变量+1,然后再进行表达式运算
后置递增:先进行表达式运算,再让变量+1
前置递减和后置递减同理
赋值运算符:将表达式的值赋给变量
比较运算符:用于表达式比较,并返回 真或者假
逻辑运算符:用于根据表达式的值返回 真或者假
逻辑非
int a = 10; cout << !a << endl; // 0 在C++中,除了0都是真 cout << !!a << endl; // 1
逻辑与:同真为真,其余为假
逻辑或:同假为假,其余为真
4 程序流程结构
C / C++ 支持最基本的三种运行结构:顺序结构,选择结构,循环结构
- 顺序结构:程序按照顺序执行,不发生跳转
- 选择结构:依照条件是否满足,有选择的 执行相应的功能
if
,三目运算符,switch
- 循环结构:依照条件是否满足,循环多次执行某段代码
while
,do ... while
,for
4.1 选择结构
if
语句单行格式
if(条件) { 条件满足执行的语句 }
注意if条件后面加上;,则{}和if条件分离,{}内无论满足或不满足都会执行
if(score >= 90); { cout << A << endl; }
多行格式
if(条件) { ... //条件满足执行的语句; } else { ... //条件不满足执行的语句 }
多条件
if(条件1){... // 条件1满足执行的语句; }else if(条件2){... // 条件2满足执行的语句; }else{...// 都不满足执行的语句 }
嵌套 筛选更加精准
if(条件1){// 如果条件1满足:if(条件2){// 条件1满足的情况下,条件2满足...} }
if
语句举例:三只小猪 找出最重的小猪#include <iostream> using namespace std; int main(){int a = 0, b = 0, c = 0;cin >> a >> b >> c;int tmp = 0;// 先比较a和b 找出相对最大值放到tmp中if(a > b) tmp = a;else tmp = b;// tmp和c比较,找出三个数的最大值if(tmp > c) cout << "max: "<< tmp <<endl;else cout << "max: "<< c <<endl;return 0; }
三目运算符:
表达式1 ? 执行语句1 : 执行语句2
表达式1为真,执行 语句1;表达式1为假,执行 语句2;int a = 10, b = 20, c = 0; int c = a > b ? a : b; //将a,b的最大值 赋值给 变量c // 在C++中,三目运算符返回的是变量,可以继续赋值 ( a > b ? a : b) = 100; // b = 100
- 可以作为右值,计算出结果给左值赋值
- 也可以将计算的结果作为左值,进行后续运算
switch
语句:执行多条件分支语句,switch(表达式){ //根据表达式的结果执行以下的分支case 结果1:执行语句;break; //退出当前分支case 结果2:执行语句;break; case 结果3:执行语句;break; default: //以上都不满足的 默认分支执行语句; }
注意:
switch
语句中的表达式类型 只能是整形或者字符型case
中,如果没有break;
,那么程序会一直向下执行- 总结:与
if
语句相比,对于多条件判断时,switch
的结构清晰且执行效率高,缺点是switch
不可以判断区间
4.2 循环结构
while循环语句:
while(循环条件) { 执行语句; }
只要循环条件结果为真,就执行循环语句// 输出0-9 int num = 0; while(num < 10){cout << num << endll;num ++; }//随机生成1个1-100数字,玩家猜测,提示玩家数字过大或过小,如果猜对恭喜玩家胜利且退出游戏 #include <ctime> //系统时间头文件 srand( (unsigned int) tim(NULL) ); //随机种子 int random = rand() % 100 + 1, num = 0; while(1){cout << "Enter the Num: "<<endl;cin >> num;if(num == random){cout << "yes" <<endl;break;}else if(num < random) cout << "num is too small"<< endl;else cout << "num is too big" << endl;; }
注意:执行循环语句时,程序必须提供跳出循环的出口,否则出现死循环
do … while 循环语句
do{ 循环语句 } while(循环条件);
先执行一次循环体,再判断循环条件// 输出0-9 int num = 0; do{cout << num << endl;num ++; }while(num < 10);//水仙花数:一个三位数,每个位上的数字的三次幂之和等于它本身 eg:153=1*1*1 + 5*5*5 + 3*3*3 //100-999 int num = 100; do{int a = num / 100;int b = num /10 % 10;int c = num % 10; //对数字取模于 10,可以获取到个位if(a*a*a + b*b*b + c*c*c == num)cout << num << endl;num++; }while(num < 1000); //153, 370, 371, 407
for循环语句
for(起始表达式;条件表达式;末尾循环体) { 循环语句; }
// 打印0-9 for(int i = 0; i < 10; i++) cout << i << endl;int i = 0 for(; i < 10; i++) cout << i << endl;int i = 0; for(; ; i++){if(i >= 10) break;cout << i << endl; }int i = 0; for(; ;){if(i >= 10) break;cout << i << endl;i++ }// 敲桌子 从1开始到100 如果数字个位/十位有7,或者该数字是7的倍数,打印敲桌子,其余数字直接打印 int num = 1; do{ int b = num % 100 / 10;int c = num % 100 % 10;if(b == 7 || c == 7 || num%7==0)cout << "knock" << endl;else cout << num << endl;num++; }while(num <= 100);
嵌套循环:循环体中再套一次循环
//打印星图 for(int i = 0; i < 10; i ++){for(int j = 0; j < 10; j++) cout << "* ";cout << endl; }
// 乘法口诀表 for(int i = 1; i < 10; i ++){for(int j = 1; j <= i; j++) // 列数 <= 当前行数printf("%d * %d= %d\t",j,i,i*j); // 先输出列数 再输出行数cout << endl; }
4.3 跳转语句
break
语句,跳出选择结构或者循环结构break
使用的时机:- 出现在
switch
条件语句中,终止case
并跳出switch
- 出现在循环语句中,作用是跳出当前循环
- 出现在嵌套循环中,跳出最近的内层循环语句
- 出现在
continue
语句,跳过本次循环中余下尚未执行的语句,继续执行下一次循环//输出0-100的奇数 for(int i = 0; i <= 100; i++){if( i%2 = 0 ) continue; //break 会退出循环,continue不会cout << i << endl; }
goto
语句,goto 标记
,可以无条件的跳转cout << "1" << endl; goto FLAG; cout << "2" << endl; cout << "3" << endl; FLAG: cout << "4" << endl; cout << "5" << endl;
5 数组
数组就是一个集合,里面存放相同类型的数据元素
特点1:数组中 每个数据元素都是相同的数据类型
特点2:数组是由连续的内存位置组成的
数组下标从0开始,通过下标,可以通过访问数组中元素
5.2 一维数组
一维数组定义方式
/* 1. 数据类型 数组名[ 数组长度 ]; 2. 数据类型 数组名[ 数组长度 ]={ 值1,值2,值3... }; 3. 数据类型 数组名[ ]={ 值1,值2,值3... }; */int arr[5]; arr[0] = 10, a[1] = 20, arr[0] = 10, a[4] = 20, a[5] = 20; //给数组中元素进行赋值 printf("%d %d %d %d %d\n",arr[0],arr[1],arr[2],arr[3],arr[4]);int arr2[5]= {10,20,30}; //如果在初始化时,{}中没有全部填写完,会用0填补剩余数据 for(int i = 0; i < 5; i++) cout << arr2[i] << "\t"; //10 20 30 0 0int arr3[] = { 1,45,78,334,78,24,55,78,230,333,5189,5269}; //int arr3[]; //报错,定义数组的时候 必须右初始长度
一维数组数组名
作用:统计整个数组的内存大小;获取数组在内存中的首地址
int arr[5] = {10,20,30,40,50}; cout << sizeof(arr) << endl; // 20 20个字节 cout << sizeof(arr[0]) << endl; // 4 4个字节 cout << sizeof(arr)/sizeof(arr[0]) << endl; //5 20/4 数组长度cout << arr << endl; // 获取数组在内存中的首地址 0x61fe00 十六进制 cout << &arr[0] << endl; //数组中第一个元素的地址 0x61fe00 首元素地址和数组首地址一样 cout << &arr[1] << endl; //数组中第二个元素的地址 0x61fe04 和上面比多了4个字节//arr = 100; //错误数组名是常量,不能修改值
数组名是常量,不可以进行赋值操作,已经指向了首地址
// 求数组中最大值
int arr[5] = {10,20,30,40,50};
int arrMax = INT_MIN;
for(int i = 0; i < 5; i++) arrMax = max(arrMax, arr[i]);
cout << arrMax << endl;// 数组元素逆置 0 1 2 3 4 0和4换,1和3换
int arr[5] = {10,20,30,40,50};
int n = sizeof(arr)/sizeof(arr[0]);
for(int i = 0; i < n/2; i++) swap(arr[i], arr[n-1-i]);// 数据元素逆置的 老师写法:
int start = 0, end = sizeof(arr)/sizeof(arr[0]) -1; // 左右双指针
while(start < end){swap(arr[start],arr[end]); // 元素交换start++,end--; // 下标更新
}
冒泡排序:数组中的数据升序排列
- 比较相邻元素,如果第一个元素比第二个元素大,就交换它们两个
- 对每一对相邻元素做相同工作,执行完毕后,找到第一个最大值
- 重复以上的步骤,每次比较次数-1,知道不需要比较
示例:将数组
{4,2,8,0,5,7,1,3,9}
n = 9,进行升序排序- 比较几轮? 八个数,只需要7轮,因为最小的数不需要再进行一轮
- 每轮确定一个最大值,那么第
i
轮中相邻元素比较n - 1 - i
次
void prints(int arr[], int n){for(int i = 0; i < n; i++) cout << arr[i] << " ";cout << endl; } void bubbleSort(int arr[], int n){for(int i = 0; i < n-1; i++){ //代表几轮for(int j = 0; j < n-1-i;j++ ){ //代表每轮的比较次数if(arr[j] > arr[j+1]) swap(arr[j],arr[j+1]);} prints(arr,n);} }
排序总论述:元素个数 - 1
每轮的对比次数: 元素个数 - 排序论述 - 1
5.3 二维数组
- 二维数组定义方式
/*
1. 数据类型 数组名[ 行数 ][ 列数 ];
2. 数据类型 数组名[ 行数 ][ 列数 ] = { {值1, 值2}, {值3, 值4} ... };
3. 数据类型 数组名[ 行数 ][ 列数 ] ={ 值1, 值2, 值3... };
4. 数据类型 数组名[ ][ 列数 ] = { 值1, 值2, 值3... }
*/int arr[2][3];
arr[0][0] = 1, arr[0][1] = 2, arr[0][2] = 3;
arr[1][0] = 4, arr[1][1] = 5, arr[1][2] = 6;int arr1[2][3] = {{1,2,3},{3,4,5}
};int arr2 [2][3] = {1,2,3,4,5,6};int arr3 [][3] = {1,2,3,4,5,6};//编译器可以自动推算 行数和列数//int arr3[][]; //报错
在定义二维数组的时候,如果初始化了数据,可以省略行数,但是不可以省略列数
二维数组的数组名称
作用:查看二维数组所占用的内存空间; 获取二维数组的首地址
/***************** 二维数组所占用的内存空间 && 行数 && 列数 ***********************/ int arr[2][3] = { {1,2,3}, {3,4,5} }; cout << sizeof(arr) << endl; // 24 二维数组的所有内存空间:6个元素 每个元素4个字节(int类型) cout << sizeof(arr[0]) << endl; // 12 二维数组一行的内存空间 cout << sizeof(arr[1]) << endl; // 12 cout << sizeof(arr[0][0]) << endl; // 4 二维数组中一个元素的内存空间 cout << sizeof(arr) / sizeof(arr[0]) << endl; //行数, 所有内存空间/一行所占内存空间 cout << sizeof(arr[0]) / sizeof(arr[0][0]) << endl; //列数,一行内存空间/一个元素的内存空间 /***************** 二维数组首地址 ***********************/ cout << arr << endl; cout << &arr[0] << endl; cout << &arr[0][2] << endl; cout << &arr[1] << endl; // 和arr[0] 相差12个字节 cout << &arr[1][2] << endl;
二维数组行数 =
sizeof(array)/sizeof(array[0]);
二维数组列数 =
sizeof(array[0])/sizeof(array[0][0]);
- 二维数组应用案例
int scores[3][3] = {100,100,100,90,50,100,60,70,80};
int n = sizeof(scores) / sizeof(scores[0]);
int m = sizeof(scores[0]) / sizeof(scores[0][0]);
string names[3] = {"ZhangSan", "LiSi", "WangWu"};for(int i = 0; i < n; i ++){int sum = 0;for(int j = 0; j < m; j++){sum += scores[i][j];}cout << "the score of " << names[i] << " is " << sum << endl;
}
6 函数
作用:将一段经常使用的代码进行封装,减少重复代码
6.2 函数的定义与调用
函数的定义一般有5个步骤:
- 返回值类型
- 函数名
- 参数列表
- 函数体
- return 表达式
// 函数定义 语法:
返回值类型 函数名 (参数列表) {函数体;return 表达式
}// 定义加法函数,返回两数相加的和 a 和 b 被称为 形参,函数定义的时候没有真实数据,只是形式上的参数
int add(int a, int b){return a + b;
}//函数调用 语法:
函数名称(参数)int main(){int x = 1, y = 2;cout << add(x,y) << endl; //函数调用 x 和 y 被称为实参,实际参数// 调用函数的时候,会把 实参的值 传递给形参return 0;
}
函数定义里的小括号称为 形参,函数调用传入的参数为 实参
6.4 值传递
- 值传递就是函数调用时,实参将数值传入给形参
- 值传递时,如果形参发生改变,并不会影响实参
//实现两个数的交换
void swap(int num1, int num2){int tmp = num1;num1 = num2;num2 = tmp;
}
int main(){int a = 10, b = 20;cout <<"Before: a = "<< a << ", b = " << b <<endl;swap(a, b);cout <<"After: a = "<< a << ", b = " << b <<endl;return 0;
}
6.5 函数的常见样式
有无参数,有无返回值
常见函数样式四种:无参无返;有参无返;无参有返;有参有返;
void test01(){ // 无参无返cout << "test01" << endl;
}void test02(int a){ // 有参无返cout <<"test02, a :" << a <<endl;
}int test03(){ // 无参有返cout <<"test03"<<endl;return 10;
}int test04(int a){ // 有参有返cout <<"test02, a :" << a <<endl;return a;
}
int main(){test01(); // 无参无返 的函数调用test02(10); // 有参无返 的函数调用int a = test03(); // 无参有返 的函数调用cout << a << endl;int b = test04(10); // 有参有返 的函数调用cout << b << endl;return 0;
}
6.6 函数的声明
作用:告诉编译器函数名称 及 如何调用函数。 函数的实际主体可以单独定义
函数的声明可以多次,但是函数的定义只能有一次
在前面声明过函数之后,函数定义的位置可以随便
函数的声明:函数类型,函数名,形参列表。 不需要函数体
int max(int a, int b); // 函数的声明,提前告诉编译器函数的存在,可以利用函数的声明
int max(int a, int b);
int max(int a, int b); // 声明可以有多次,但是定义只能有一次int main(){int a = 10, b = 20;cout << max(a, b) << endl;
}int max(int a, int b){ // 函数的定义return a > b ? a : b;
}
6.7 函数的分文件编写
作用:让代码结构更加清晰
函数分文件编写一般四个步骤:
- 创建后缀名 为 .h 的头文件
- 创建后缀名为 .cpp 的源文件
- 在头文件中写函数的声明 .h
- 在源文件中写函数的定义 .cpp
// addTwoNum.h
#include <iostream>
using namespace std;int add(int num1, int num2);
// addTwoNum.cpp
#include "addTwoNum.h" // 加入我们自己自定义的头文件
int add(int num1, int num2){return num1 + num2;
}
// main.cpp
#include "addTwoNum.h"
#include "addTwoNum.cpp"int main(){int a = 10, b = 20;cout << " a + b = "<< add(a,b) <<endl;return 0;
}
7 指针
指针作用:可以通过指针间接访问内存
- 内存编号是从0开始记录的,一般用十六进制数字表示
- 可以利用指针变量保存地址,指针就是一个地址
7.2 指针变量的定义和使用
int a = 10;
// 1.指针定义。语法:数据类型 *指针变量名
int *p;
// 让指针记录变量 a 的地址
p = &a;//2.指针使用。可以使用解引用的方式来找到指针指向的内存
// 指针前加上* 代表解引用,找到指针指向的内存中的数据
*p = 1000;
cout << a << endl; // a = 1000
指针定义:
int *p = &a;
指针使用:
*p = 1000;
提取或者修改数据的值
7.3 指针所占的内存空间
提问:指针也是一种数据类型,那么这种数据类型占用多少内存空间
指针存放的是 地址,十六进制值,
32位操作系统 指针占用4个字节,64位操作系统 指针占用8个字节 ,与数据类型无关
cout << sizeof(int *) << endl;
cout << sizeof(long long *) << endl;
cout << sizeof(float *) << endl;
cout << sizeof(double *) << endl;
cout << sizeof(char *) << endl;
7.4 空指针和野指针
空指针:指针变量指向内存中编号 为0 的空间
用途:初始化指针变量
注意:空指针指向的内存是不可以被访问的,因为0~255之间的内存编号是系统占用的,不可以访问
int *p = nullptr;
//*p = 100; // 报错:引发异常,写入访问权限冲突,p是nullptr 0~225之间的内存编号是系统占用,不可访问
// cout << *p << endl; //同样报错
野指针:指针变量指向非法的内存空间 (不是我们申请的)
在程序中,要尽量避免出现野指针
int *p = (int *) 0x1100; // 随便找一个十六进制赋值
// cout << *p << endl; // 报错:读取访问权限冲突
空指针和野指针都不是我们申请的空间,因此不要访问
7.5 const 修饰指针
三种情况:
const
修饰指针 —— 常量指针 修饰的是 指针指向的值,即指向的数据const
修饰常量 —— 指针常量 修饰的是 p保存的值,即地址cosnt
即修饰指针,又修饰常量
const
常量,*
指针,英文翻译成中文 就是其名字,按照顺序
const int * p;
- 翻译:常量指针
- 修饰:
const
后面是int *p
,因此const
是 修饰*p
的值,即指向的数据不变
int * const p;
翻译:指针常量
修饰:
const
后面是p
,因此const
修饰的是p
的值,即指针自己的值不变 即地址不变
/*********************** 指针前面放const,是常量指针 ***********************/
int a = 10, b = 10;
const int *p = &a; //指针的指向可以修改(可以指向b),指针指向的值不可以修改(a的值不能修改)
*p = &b; // √
*p = 20; // ×/*********************** 变量前面放const,是指针常量 ***********************/
int * const p = &a; //指针的指向不可以改,指针指向的值可以修改
*p = b; // ×
*p = 20; // √/*********************** 即修饰指针,又修饰常量 ***********************/
const int * const p = &a; //指针的指向,指针指向的值 都不可以修改
*p = b; // ×
*p = 20; // ×
7.6 指针和数组
作用:利用指针访问数组中元素
int arr[] = {1,2,3,4,5,6,7,8,9,10};
cout << "the first element: " << arr[0] << endl; // 1int *p = arr; // arr就是数组首地址, 数组第一个元素的地址
cout << "the first element: " << *p << endl; // 1 用指针访问第一个元素
// 让指针向后移动四个字节则可以访问第二个元素
cout << "the second element: " << *(++p) << endl; // 2// 利用指针访问数组中所有元素
int n = sizeof(arr) / sizeof(arr[0]);
int *p1 = arr;
for(int i = 0; i < n; i++){// cout << arr[i] << " ";cout << *p1++ << " ";
}
p++
可以把 p
向后移动4个字节
思考:如果 数组是double类型呢,此时p 也是double类型,p++是向前移动8个字节
7.7 指针和函数
作用:利用指针作为函数参数,可以修改实参的值
// 值传递:实现两个数字的交换 形参的数发生了交换,但是实参没有改变
void swap(int a, int b){int tmp = a;a = b;b = tmp;
}
// 指针传递
void swap1(int *a, int *b){int tmp = *a;*a = *b;*b = tmp;
}int main(){int i = 10, j = 20;printf("i = %d, j = %d\n", i , j);swap(i, j); // 值传递printf("After swap(), i = %d, j = %d\n", i , j); // After swap(), i = 10, j = 20swap1(&i, &j); // 地址传递printf("After swap1(), i = %d, j = %d\n", i , j);// After swap1(), i = 20, j = 10return 0;
}
内存分析:
初始条件: a 变量, 值 10, 地址0x0011
b 变量, 值 20, 地址0x0022
*p1 存放 a变量的地址值 0x0011, *p2 存放 b变量的地址值 0x0022
temp 临时整形变量 ,* 解引用操作符访问指针p1所指向的值,即 10
将 p1 指向的值,修改为 p2 指向的值,即 20
再把 p2 指向的值,修改为temp的值,即 10
7.8 指针、数组、函数
值传递的例外,数组可以通过值传递修改 实参数组元素的值
通常,对于一般变量的值传递,函数会拷贝一个与实参值相同的临时变量形参 在函数体中使用,因此在函数内部改变形参 并不会 改变 原数组中的实际值
但是对于数组来说,通过值传递的函数,可能够改变原数组的实际值
原因:函数传参依旧是值传递,但拷贝的是数组首元素地址的临时指针
因此对于数组的 “值传递” 传参的函数,函数内部对于数组元素的改变是真实存在的
补充:参数内
int arr[]
省略数据具体大小的原因是,实际传参为数组首元素的的指针,与数组实际大小无关
#include <iostream>
using namespace std;
void prints(int arr[], int len){for(int i = 0; i < len; i++) cout <<arr[i] << "\t";
}
void sets(int arr[]){ // 值传递arr[0] = 999;
}int main(){int arr[5]{9,3,4,6,7};sets(arr);prints(arr,5);return 0;
}
案例
函数传入数组
prints(int *arr){..}
,调用时传入prints(arr);
数组名字(数组首地址)即可案例描述:封装一个函数,利用冒泡排序,实现对整型数组的升序排序
给定数组:
int arr[10] = {4,3,6,9,1,2,10,8,7,5};
#include <iostream> using namespace std;void prints(int *arr, int n){for(int i = 0; i < n; i++) cout << *arr++ <<" ";cout << endl; } void swap(int *a, int *b){int tmp = *a;*a = *b;*b = tmp; } void bubbleSort(int *arr, int n){for(int i = 0; i < n - 1; i++){for(int j = 0; j < n - 1 - i; j++){if(arr[j] > arr[j+1]) swap(&arr[j], &arr[j+1]);}} } int main(){int arr[] = {4,3,6,9,1,2,10,8,7,5};int n = sizeof(arr)/sizeof(arr[0]);prints(arr, n); // 此刻传入的时候 不用&arr,因为arr就是数据首地址 已经是地址了!bubbleSort(arr, n);prints(arr, n);system("pause");return 0; }
函数需要传入数组:
函数声明定义时形参是数组类型指针。
函数调用传入数组名,即数组首地址
8 结构体
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。
用户自定义
系统内置类型:short,int,long,long long, float double, bool, char 等
8.2 结构体定义和使用
结构体的定义:struct 类型名称 { 成员列表 };
结构体的使用(通过结构体创建变量):
struct 结构体名 变量名;
struct 结构体名 变量名 = {成员1值, 成员2值, ...};
struct 类型名称 { 成员列表 } 变量名;
定义结构体时 顺便创建变量
// 1. 结构体定义,创建学生数据类型。 学生包括:姓名,年龄,分数
struct Student{ string name;int age;int score;
}st3;// 2. 结构体使用,通过学生类型创建具体学生
int main(){// 2.1 struct Student st1;struct Student st1; // struct关键字可以省略st1.name = "Mary";st1.age = 18;st1.score = 100;// 2.2 struct Student st2 = { ... }struct Student st2 = {"Bob", 20, 90};// 2.3 在定义结构体时顺便创建结构体变量 st3st1.name = "Joy";st1.age = 19;st1.score = 60;return 0;
}
申请创建结构体变量的struct关键字可以省略,定义结构体的struct不可以省略
结构体变量,利用操作符
.
访问成员
8.3 结构体数组
作用:将自定义的结构体放到数组中,方便维护
语法:struct 结构体名 数组名[元素个数] = { { }, { }, { } ... { } };
struct Student{ // 1. 定义结构体string name;int age;int score;
};int main{// 2. 创建结构体数组struct Student stArray[3] = {{"Amy",18,100},{"David",27,60},{"Rachel",16,70} };// 3. 给结构体数组中元素赋值或修改stArray[2].score = 90;// 4. 遍历结构体数组for(int i = 0; i < 3; i++) cout <<"Name:"<<stArray[i].name<<",age:"<<stArray[i].age<<",score:"<<stArray[i].score<<endl;return 0;
}
8.4 结构体指针
作用:通过指针访问结构体中的成员,利用操作符->
可以实现通过结构体指针访问结构体属性
struct Student{string name;int age;int score;
};
int main(){// 1. 创建学生结构体变量struct Student st = {"Rachel",16,70};// 2. 指针指向结构体变量struct Student *p = &st;// 3. 通过指针访问结构体变量中的数据 符号->cout <<"Name:"<<p->name<<",age:"<<p->age<<",score:"<<p->score<<endl;
}
8.4.1 结构体的所占内存空间——内存字节对齐
计算机内存是以字节(Byte)为单位划分的,理论上 CPU 可以访问任意编号的字节,但实际情况并非如此。
CPU 通过地址总线来访问内存,一次能处理几个字节的数据,就命令地址总线读取几个字节的数据。32 位的 CPU 一次可以处理 4 个字节的数据,那每次就从内存读取 4 个字节的数据,同理64 位的处理器每次读取 8 个字节。
以 32 位的 CPU 为例,实际寻址的步长为 4 个字节,也就是只对编号为 4 的倍数的内存寻址,例如 0、4、8、 12、1000 等,而不会对编号为 1、3、11、1001 的内存寻址,做到以最快的速度寻址
对于程序来说,一个变量最好位于一个寻址步长的范围内,这样一次就可以读取到变量的值;如果跨步长存储, 就需要读取两次,然后再拼接数据,效率显然降低了。
将一个数据尽量放在一个步长之内,避免跨步长存储,这称为内存对齐。在 32 位编译模式下,默认以 4 字节对 齐;在 64 位编译模式下,默认以 8 字节对齐,为了提高存取效率,编译器会自动进行内存对齐。
#include <iostream>
using namespace std;struct Student{ char Firstname;short age;int score;
};struct Student1{short age;int score;char Firstname;
};
int main(){// 1. 创建学生结构体变量struct Student st = {'b',16,70};struct Student1 st1 = {16,70,'b'};printf("char\tshort\tint\n");printf("%d\t%d\t%d\t all sizeof: %d\n",sizeof(st.Firstname),sizeof(st.age),sizeof(st.score),sizeof(st));printf("%X\t%X\t%X\n", &st.Firstname, &st.age, &st.score);printf("\nshort\tint\tchar\n");printf("%d\t%d\t%d\t all sizeof: %d\n",sizeof(st1.age),sizeof(st1.score),sizeof(st1.Firstname),sizeof(st1));printf("%X\t%X\t%X\n", &st1.age, &st1.score, &st1.Firstname);return 0;
}
编译器在 age
和 score
之间插入了 1 字节的填充,以保证对齐。因此sizeof(Student)
的结果为 8 字节
编译器在 age
和 score
之间插入了 2 字节的填充,以保证对齐。因此sizeof(Student1)
结果为 12 字节
char
类型的变量Firstname
占据 1 字节
short
类型的变量age
占据 2 字节
int
类型的变量score
占据 4 字节。gcc中默认
#pragma pack(4)
,且结构体中最长数据类型为4个字节,所以有效对齐单位为4个字节规则:
1.基本类型的对齐值就是其sizeof值;
2.结构体的对齐值是其成员的最大对齐值;
3.编译器可以设置一个最大对齐值,怎么类型的实际对齐值是该类型的对齐值与默认对齐值取最小值得来。
结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。
结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
8.5 结构体嵌套结构体
作用:结构体中的成员可以是另一个结构体
例如:每个老师辅导一个学员,一个老师的结构体中 有一个学生结构体
#include <iostream>
using namespace std;struct Student{ // 定义学员结构体string name;int age;int score;
};struct Teacher{ // 定义教师结构体int id;string name;int age;struct Student stu;
};int main(){struct Teacher t;t.id = 1, t.name = "Mrs.Wang", t.age = 35;t.stu.name = "Amy", t.stu.age = 18, t.stu.score = 100;printf("teacherID:%d\tteacherName:%s\tteacherAge:%d\n",t.id,t.name.c_str(),t.age);printf("studentName:%s\tstudentAge:%d\tstudentScore:%d\n",t.stu.name.c_str(), t.stu.age, t.stu.score);return 0;
}
在结构体中可以定义另一个结构体作为成员,用来解决实际问题
8.6 结构体做函数参数
将结构体作为参数向函数中传递
传递方式有两种:
- 值传递(不改变实参的值,只改变形参的值)
- 地址传递 (改变实参的值)
// 结构体作为函数参数,打印传入的结构体信息
#include <iostream>
using namespace std;struct Student{ // 定义学员结构体string name;int age;int score;
};void printStd1(struct Student s){ // 值传递 打印结构体信息printf("Name:%s\tAge:%d\tScore:%d\n",s.name.c_str(), s.age, s.score);
}void printStd2(struct Student *s){// 地址传递 打印结构体信息printf("Name:%s\tAge:%d\tScore:%d\n",s->name.c_str(), s->age, s->score);
}
void setStdName1(struct Student s){ // 值传递 改变结构体 属性name, 修改实参失败s.name = "Rachel";
}
void setStdName2(struct Student *s){ // 地址传递 改变结构体 属性name,修改实参成功s->name = "Rachel";
}
int main(){struct Student std {"Amy",18,100};setStdName1(std);printStd1(std);setStdName2(&std);printStd2(&std);return 0;
}
值传递和地址传递的不同,值传递不修改实参,地址传递修改实参
8.7 结构体中 const使用场景
作用:用const
防止误操作
考虑有成千上万个学生,需要打印学生的信息,用值传递还是地址传递呢?
此时需要使用地址传递,可以减少内存空间,不会复制新的副本出来
如果只是打印信息,不需要修改指针所指向的数据,则加上const关键字,防止误操作
struct Student{ string name;int age;int score;
};
void printStd(const struct Student *s){// 地址传递 打印结构体信息// s->name = "Amy"; // 编译器报错 常量只可读,防止了误操作printf("Name:%s\tAge:%d\tScore:%d\n",s->name.c_str(), s->age, s->score);
}
int main(){struct Student std {"Amy",18,100};printStd(&std);return 0;
}
8.8 结构体案例
案例1
#include <iostream>using namespace std;struct Student { // 定义学员结构体string name;int age;int score; };struct Teacher { // 定义教师结构体int id;string name;struct Student sArray[5]; };void setAllInfo(struct Teacher tArray[], int n, int m) {for (int i = 0; i < n; i++){tArray[i].id = i;tArray[i].name += ("teacher" + to_string(i));for (int j = 0; j < m; j++){tArray[i].sArray[j].name += ("student" + to_string(j));tArray[i].sArray[j].age = 19;tArray[i].sArray[j].score = 80;}} }void PrintInfo(struct Teacher tArray[], int n, int m) {for (int i = 0; i < n; i++){printf("TeacherID:%d\tTeacherName:%s\n", tArray[i].id, tArray[i].name.c_str());for (int j = 0; j < m; j++){printf("\tStdName:%s\tStdAge:%d\tStdScore:%d\n", tArray[i].sArray[j].name.c_str(), tArray[i].sArray[j].age, tArray[i].sArray[j].score);}} }int main() {// 1. 创建3名老师的数组// 2. 通过函数给3名老师的信息赋值,并给老师带的学生信息赋值// 3. 打印老师和所带学生信息struct Teacher tArray[3];// tArray 720 ; tArray[0] 240; tArray[0].sArray 200,tArray[0].sArray[0] 40int n = sizeof(tArray) / sizeof(tArray[0]);int m = sizeof(tArray[0].sArray) / sizeof(tArray[0].sArray[0]);setAllInfo(tArray, n, m); PrintInfo(tArray, n, m); //是地址传递,元素名是地址。但是函数中承接的不是指针,而是值return 0; }
值传递的例外,数组可以通过值传递修改 实参数组元素的值
详情内容在 7.8 章 指针与数组
案例2
#include <iostream>using namespace std;struct Hero{string name;int age;string gender; };void bubbelSort(struct Hero hArray[], int len){for(int i = 0; i < len - 1; i++){for(int j = 0; j < len - 1 - i; j++){if(hArray[j].age > hArray[j+1].age){// 交换两位英雄的信息swap(hArray[j], hArray[j+1]);/* 老师写法:struct Hero tmp = hArray[j];hArray[j] = hArray[j+1];hArray[j+1] = tmp;*/}}} } void swap(struct Hero *h1, struct Hero *h2){struct Hero tmp = *h1;*h1 = *h2;*h2 = tmp; }void printsHero(struct Hero hArray[], int len){for(int i = 0; i < len; i++){printf("%s %d %s\n",hArray[i].name.c_str(), hArray[i].age,Array[i].gender.c_str());} } int main() {struct Hero hArray[5] = {{"LiuBei",23,"man"},{"GuanYu",22,"man"},{"ZhangFei",20,"man"},{"ZhaoYun",21,"man"},{"DiaoChan",19,"woman"}};int len = sizeof(hArray) / sizeof(hArray[0]);cout << "Before BubbleSort: "<<endl;printsHero(hArray, len);bubbelSort(hArray, len);cout << "\n\nAfter BubbleSort: "<<endl;printsHero(hArray, len);return 0; }
实战:通讯录管理系统
主要功能
- 添加联系人:向通讯录中添加新人信息(姓名、性别、年龄、联系电话、家庭住址),最多记录1000人
- 显示联系人:显示通讯录中所有联系人信息
- 删除联系人:按照姓名删除 指定联系人
- 查找联系人:按照姓名查看 指定联系人信息
- 修改联系人:按照姓名修改 执行联系人信息
- 清空通讯录:清空通讯录中所有信息
- 退出通讯录:退出当前使用的通讯录
1. 整体框架搭建
菜单
- 封装函数
void showMenu()
显示界面 - 在 main 函数中调用封装好的函数
- 封装函数
退出功能
根据用户不同选择,进入不同功能,可以选择switch分支结构体 构建架构
当用户选择 0 的时候,执行退出
#include <iostream> using namespace std;void showMenu(){ //菜单功能cout <<"**************************************"<<endl;cout <<"********** 1. 添加联系人 **********"<<endl;cout <<"********** 2. 显示联系人 **********"<<endl;cout <<"********** 3. 删除联系人 **********"<<endl;cout <<"********** 4. 查找联系人 **********"<<endl;cout <<"********** 5. 修改联系人 **********"<<endl;cout <<"********** 6. 清空通讯录 **********"<<endl;cout <<"********** 0. 退出通讯录 ***********"<<endl;cout <<"**************************************"<<endl;cout <<"请输入您要操作的号码: "; }int main(){ int choice = 0;while(true){showMenu();cin >> choice;switch(choice){case 0:cout <<"已退出,欢迎您的下次使用!" << endl;system("pause");return 0; // 结束main函数break; // 0. 退出通讯录case 1:break; // 1. 添加联系人case 2:break; // 2. 显示联系人case 3:break; // 3. 删除联系人case 4:break; // 4. 查找联系人case 5:break; // 5. 修改联系人case 6:break; // 6. 清空通讯录}}system("pause");return 0; }
2. 具体功能实现
2.1 添加联系人
设计联系人结构体,设计通讯录结构体(长度不能超过1000)
struct Person{ // 联系人结构体:姓名、性别、年龄、联系电话、家庭住址string m_Name;int m_Sex;int m_Age;string m_Phone;string m_Addr; };struct AddressBooks{ // 通讯录结构体struct Person[Max]; // 通讯录中保存的联系人数组int m_Size; // 通讯录中人员个数 };
main 函数中创建通讯录
struct AddressBooks abs; // 创建通讯录结构体变量 abs.size = 0; // 初始化属性值
封装添加联系人函数
addPerson(&abs);
- 判断通讯录是否已满,如果满了就不再添加
- 否则根据m_size索引进行添加,添加完毕后 更新索引m_size
void addPerson(struct AddressBooks *p){if(p->m_Size == Max) {printf("添加失败,通讯录已满.\n");return;}string name;int sex; // 1男 2女int age;string phone;string addr;cout <<"请输入添加联系人的 姓名:";cin >> name;p->pArray[p->m_Size].m_Name = name;cout <<"请输入添加联系人的 性别(1女 2男):";cin >> sex;while(sex!=1 && sex!=2){cout << "性别格式错误,请重新输入!"<<endl;cout <<"请输入添加联系人的 性别(1女 2男):";cin >> sex;}p->pArray[p->m_Size].m_Sex = sex;cout <<"请输入添加联系人的 年龄:";cin >> age;while(age <= 0 && age <= 120){cout << "年龄格式错误,请重新输入!"<<endl;cout <<"请输入添加联系人的 年龄:";cin >> age;}p->pArray[p->m_Size].m_Age = age;cout <<"请输入添加联系人的 电话号码:";cin >> phone;p->pArray[p->m_Size].m_Phone = phone;cout <<"请输入添加联系人的 家庭住址:";cin >> addr;p->pArray[p->m_Size].m_Addr = addr;p->m_Size ++;cout << "添加成功" <<endl;system("pause"); // 请按任意键继续system("cls"); //清屏操作 }
2.2 显示联系人
判断如果当前通讯录中没有人员,就提示记录为空,人数大于0,显示通讯录中信息
void showAddrBooks(const AddressBooks *p){if(p->m_Size == 0){cout <<"当前通讯录为空"<<endl;system("pause"); // 请按任意键继续system("cls"); //清屏操作return;}for(int i = 0; i < p->m_Size; i++){cout << "姓名:"<<p->pArray[i].m_Name <<"\t\t";cout << "性别:"<<(p->pArray[i].m_Sex==1?"女":"男")<<"\t\t";cout << "年龄:"<<p->pArray[i].m_Age <<"\t\t";cout << "电话:"<<p->pArray[i].m_Phone <<"\t\t";cout << "住址:"<<p->pArray[i].m_Addr <<endl;}system("pause"); // 请按任意键继续system("cls"); //清屏操作
};
2.3 删除联系人
按照姓名进行删除指定联系人,实现步骤:
检测联系人是否存在, 封装 检测联系人 为函数
根据姓名进行查找:
- 如果存在,返回联系人在通讯录中的索引
- 如果不存在,返回 -1
int isExist(const AddressBooks *p, string name){for(int i = 0; i < p->m_Size; i++){if(p->pArray[i].m_Name == name) return i;}return -1; }
删除联系人函数
如果联系人存在,进行删除操作:把索引后的所有元素向前移动,更新数组长度
如果联系人不存在,提示查无此人后退出
void deletePerson(AddressBooks *p){cout <<"请输入要删除的联系人姓名:";string name;cin >> name;int idx = isExist(p, name);if(idx != -1){for(int i = idx; i < p->m_Size; i++){p->pArray[i] = p->pArray[i+1];}p->m_Size--;cout <<"删除成功"<<endl;}else{cout <<"查无此人" << endl;system("pause"); // 请按任意键继续system("cls"); //清屏操作return;}system("pause"); // 请按任意键继续system("cls"); //清屏操作return; }
删除前:
删除不存在的联系人
进行删除
删除后
2.4 查找联系人
按照姓名查看指定联系人的信息
- 封装 查找联系人函数
- 测试 查找指定联系人
判断用户指定的联系人是否存在
如果存在则显示信息
如果不存在则提示查无此人
void FindPerson(AddressBooks *p){cout <<"请输入要查找的联系人姓名:";string name;cin >> name;int idx = isExist(p, name);if(idx != -1){cout << "姓名:"<<p->pArray[idx].m_Name <<"\t\t";cout << "性别:"<<(p->pArray[idx].m_Sex==1?"女":"男")<<"\t\t";cout << "年龄:"<<p->pArray[idx].m_Age <<"\t\t";cout << "电话:"<<p->pArray[idx].m_Phone <<"\t\t";cout << "住址:"<<p->pArray[idx].m_Addr <<endl;}else{cout <<"查无此人" << endl;}system("pause"); // 请按任意键继续system("cls"); //清屏操作return; }
- 查找不存在的人:
- 查找存在的人:
- 查找不存在的人:
2.5 修改联系人
给姓名,如果联系人存在则修改联系人,联系人不存在则提示查无此人
修改不存在的人:
修改存在的人:修改小红的信息
2.6 清空通讯录的所有信息
清空通讯录的所有信息
实现思路:将通讯录所有联系人的信息清除掉,只要将通讯录记录的联系人数置为0即可。
void ClearAddrBooks(AddressBooks *p){p->m_Size = 0;cout << "通讯录清空" << endl;system("pause"); // 请按任意键继续system("cls"); //清屏操作return;
}
结构体在栈上分配内存,栈上的内存空间会自动释放,不需要考虑空间问题
通过将
m_Size
设置为0,逻辑上清空通讯录,并不会导致内存问题因为 通讯录申请的内存是静态分配的 分配在栈上,即在程序编译时就分配了固定大小的内存空间。
对结构体数组 赋值或者不赋值,空间大小都是那么大,所以也不需要将每个联系人的各个属性都重置为0
之后新的数据会直接在 相同的内存大小 上面进行覆盖
只有在使用动态内存分配(如
malloc()
或new
)分配内存时,需要确保正确释放内存,以避免内存泄漏。我们的代码中并没有涉及动态内存分配,因此不需要手动释放内存。同时,逻辑上清空通讯录后,之前存储的联系人信息将不再可用(无法通过m_size获取到)。如果需要重新使用通讯录,则需要重新添加联系人的信息。
所有代码:
#include <iostream>
#define Max 1000 // 定义最大人数
using namespace std;struct Person{ // 联系人结构体:姓名、性别、年龄、联系电话、家庭住址string m_Name;int m_Sex; // 1男 2女int m_Age;string m_Phone;string m_Addr;
};struct AddressBooks{ // 通讯录结构体struct Person pArray[Max]; // 通讯录中保存的联系人数组int m_Size; // 通讯录中人员个数
};void showMenu(){ //菜单功能cout <<"**************************************"<<endl;cout <<"********** 1. 添加联系人 **********"<<endl;cout <<"********** 2. 显示联系人 **********"<<endl;cout <<"********** 3. 删除联系人 **********"<<endl;cout <<"********** 4. 查找联系人 **********"<<endl;cout <<"********** 5. 修改联系人 **********"<<endl;cout <<"********** 6. 清空通讯录 **********"<<endl;cout <<"********** 0. 退出通讯录 ***********"<<endl;cout <<"**************************************"<<endl;cout <<"请输入您要操作的号码: ";
}void addPerson(struct AddressBooks *p){if(p->m_Size == Max) {printf("添加失败,通讯录已满.\n");}else{string name;int sex; // 1男 2女int age;string phone;string addr;cout <<"请输入添加联系人的 姓名:";cin >> name;p->pArray[p->m_Size].m_Name = name;cout <<"请输入添加联系人的 性别(1女 2男):";cin >> sex;while(sex!=1 && sex!=2){cout << "性别格式错误,请重新输入!"<<endl;cout <<"请输入添加联系人的 性别(1女 2男):";cin >> sex;}p->pArray[p->m_Size].m_Sex = sex;cout <<"请输入添加联系人的 年龄:";cin >> age;while(age <= 0 && age <= 120){cout << "年龄格式错误,请重新输入!"<<endl;cout <<"请输入添加联系人的 年龄:";cin >> age;}p->pArray[p->m_Size].m_Age = age;cout <<"请输入添加联系人的 电话号码:";cin >> phone;p->pArray[p->m_Size].m_Phone = phone;cout <<"请输入添加联系人的 家庭住址:";cin >> addr;p->pArray[p->m_Size].m_Addr = addr;p->m_Size ++;cout << "添加成功" <<endl;}system("pause"); // 请按任意键继续system("cls"); //清屏操作
}void showAddrBooks(const AddressBooks *p){if(p->m_Size == 0){cout <<"当前通讯录为空"<<endl;}else{for(int i = 0; i < p->m_Size; i++){cout << "姓名:"<<p->pArray[i].m_Name <<"\t\t";cout << "性别:"<<(p->pArray[i].m_Sex==1?"女":"男")<<"\t\t";cout << "年龄:"<<p->pArray[i].m_Age <<"\t\t";cout << "电话:"<<p->pArray[i].m_Phone <<"\t\t";cout << "住址:"<<p->pArray[i].m_Addr <<endl;}}system("pause"); // 请按任意键继续system("cls"); //清屏操作
};int isExist(const AddressBooks *p, string name){for(int i = 0; i < p->m_Size; i++){if(p->pArray[i].m_Name == name) return i;}return -1;
}void deletePerson(AddressBooks *p){cout <<"请输入要删除的联系人姓名:";string name;cin >> name;int idx = isExist(p, name);if(idx != -1){for(int i = idx; i < p->m_Size; i++){p->pArray[i] = p->pArray[i+1];}p->m_Size--;cout <<"删除成功"<<endl;}else{cout <<"查无此人" << endl;}system("pause"); // 请按任意键继续system("cls"); //清屏操作return;
}void FindPerson(AddressBooks *p){cout <<"请输入要查找的联系人姓名:";string name;cin >> name;int idx = isExist(p, name);if(idx != -1){cout << "姓名:"<<p->pArray[idx].m_Name <<"\t\t";cout << "性别:"<<(p->pArray[idx].m_Sex==1?"女":"男")<<"\t\t";cout << "年龄:"<<p->pArray[idx].m_Age <<"\t\t";cout << "电话:"<<p->pArray[idx].m_Phone <<"\t\t";cout << "住址:"<<p->pArray[idx].m_Addr <<endl;}else{cout <<"查无此人" << endl;}system("pause"); // 请按任意键继续system("cls"); //清屏操作return;
}void modifyPerson(AddressBooks *p){cout <<"请输入要修改的联系人姓名:";string name;cin >> name;int idx = isExist(p, name);if(idx != -1){string name;int sex; // 1男 2女int age;string phone;string addr;cout <<"请输入更新后联系人的 姓名:";cin >> name;p->pArray[idx].m_Name = name;cout <<"请输入更新后 联系人的 性别(1女 2男):";cin >> sex;while(sex!=1 && sex!=2){cout << "性别格式错误,请重新输入!"<<endl;cout <<"请输入更新后联系人的 性别(1女 2男):";cin >> sex;}p->pArray[idx].m_Sex = sex;cout <<"请输入更新后联系人的年龄:";cin >> age;while(age <= 0 && age <= 120){cout << "年龄格式错误,请重新输入!"<<endl;cout <<"请输入更新后联系人的 年龄:";cin >> age;}p->pArray[idx].m_Age = age;cout <<"请输入更新后联系人的 电话号码:";cin >> phone;p->pArray[idx].m_Phone = phone;cout <<"请输入更新后联系人的 家庭住址:";cin >> addr;p->pArray[idx].m_Addr = addr;cout << "修改成功" <<endl;}else{cout <<"查无此人" << endl;}system("pause"); // 请按任意键继续system("cls"); //清屏操作return;
}void ClearAddrBooks(AddressBooks *p){p->m_Size = 0;cout << "通讯录清空" << endl;system("pause"); // 请按任意键继续system("cls"); //清屏操作return;
}int main()
{ struct AddressBooks abs; // 创建通讯录结构体变量abs.m_Size = 0; // 初始化属性值int choice = 0;while(true){showMenu();cin >> choice;switch(choice){case 0:cout <<"已退出,欢迎您的下次使用!" << endl;system("pause");return 0; // 结束main函数break; // 0. 退出通讯录case 1:addPerson(&abs);break; // 1. 添加联系人case 2:showAddrBooks(&abs);break; // 2. 显示联系人case 3:deletePerson(&abs);break; // 3. 删除联系人case 4:FindPerson(&abs);break; // 4. 查找联系人case 5:modifyPerson(&abs);break; // 5. 修改联系人case 6:ClearAddrBooks(&abs);break; // 6. 清空通讯录}}system("pause");return 0;
}
【黑马程序员匠心之作|C++教程】C++基础入门、通讯录管理系统相关推荐
- 黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难——讲义
基础课部分讲义 核心课部分讲义 提高课部分讲义 黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难--视频链接
- 黑马程序员匠心之作|C++教程从0到1入门编程(60 指针-const修饰指针61 指针-指针和数组62 指针-指针和函数63 指针-指针配合数组和函数的案例)
黑马程序员匠心之作|C++教程从0到1入门编程(60 指针-const修饰指针61 指针-指针和数组62 指针-指针和函数63 指针-指针配合数组和函数的案例) 一.60 指针-const修饰指针 二 ...
- 练习 ~黑马程序员匠心之作-第二阶段实战-P72~P83-通讯录管理系统
黑马程序员匠心之作|C++教程从0到1入门编程 关于P72~P83-练习-通讯录管理系统 1.系统功能介绍 2.创建项目 3.菜单功能 4.退出功能实现 5.添加联系人-结构体设计 6.添加联系人-功 ...
- 黑马程序员匠心之作|C++教程从0到1入门编程-c++核心编程
内存四大区域: 运行前: 全局区(存放全局变量,静态变量,常量) ,常量区中存放 const修饰的全局常量和字符串常量,静态变量为static修饰的变量 代码区(存放二进制机器指令,特点:共享与只读) ...
- 黑马程序员匠心之作|C++教程从0到1入门编程(基础)
1 C++初识 1.1 第一个C++程序 编写一个C++程序总共分为4个步骤: 创建项目 创建文件 编写代码 运行程序 1.1.1 C++程序通用模块 #include <iostream> ...
- 【SSM面向CRUD编程专栏 3】关于黑马程序员最全SSM框架教程视频,P37集老师跳过的模块创建以及tomcat下载安装配置和运行等诸多问题
写在前面: 本人是在学习B站黑马程序员SSM框架教程视频的时候在P37集遇到了问题,如果不解决还没办法往下接着听,老师跳过的模块创建以及tomcat下载安装配置和运行等诸多问题,全在 ...
- 黑马程序员前端培训:高效的前端编程入门训练方法
如今,"前端"这个词已经成为一个大方向的概念,其涵盖的范围可以说非常广:比如浏览器的网页开发.移动App开发.桌面应用开发等等.但是,立足到每一个具体的问题上,前端开发都需要使用到 ...
- 黑马程序员_IOS开发简介和C语言基础
(一)IOS开发简介: (1)IOS开发: IOS开发就是开发运行在IOS环境系统的软件.apple公司2009年推出开发包和环境,国内2010年iphone4问世,从而相 继出现开发者---> ...
- 软件测试黑马程序员课后答案_软件测试教程课后答案
软件测试教程课后答案 [篇一:软件测试习题答案] > 一.简答题和应用题: 1 测试人员面试题 01 .为什么要在一个团队中开展软件测试工作? 因为没有经过测试的软件很难在发布之前知道该软件的质 ...
最新文章
- 基于Matlab和Wind SQL数据库的通用选股策略回测程序
- 多视图几何三维重建实战系列之R-MVSNet
- module compiled against API version 0xb but this version of numpy is 0xa
- 怎么让项目断开svn连接服务器,SVN断开与服务器连接
- 通过Exchange online实现HAB功能
- QT-QT简介,QT环境与工具链(day1)
- [css] 你知道全屏滚动的原理是什么吗?它用到了CSS的哪些属性?
- c语言 rbinom函数,R语言系列:常见离散分布及相关函数
- IE9 表格错位bug
- SQLServer2016安装教程
- GetLastError错误码大全
- quantization 顶会文章简介 2017
- unity学习、unity培训、unity企业培训、U3D资源、U3D培训视频U3D教程、U3D常见问题、U3D项目源码
- 微信小程序联盟:官方文档+精品教程+demo集合(12月更新……)
- 并发编程-初级之认识并发编程
- java全栈系列之JavaSE--JDK的安装以及环境搭建001
- 天涯明月刀手游服务器版本信息,天涯明月刀手游合服计划公告
- Linux输入输出系统原理笔记
- mysql 以小时 分钟 为单位分组 30分钟 4小时
- 深入浅出通信原理2021-03-07
热门文章
- GO语言内存逃逸图文分析
- MySQL中删除数据的两种方法_MySQL删除数据库的两种方法
- OpenCV综合练习1——水瓶水位线合格检测
- conda创建虚拟环境安装python类库
- “扎根”信创,华云数据许广彬:做政企上云背后的力量
- rsem比对_科学网—FPKM, RPKM, RPM以及TPM的关系之见解 - 江纯阶的博文
- fork()函数与vfork()函数的区别。
- CSS position 定位之 Flex
- matlab中psnr多了50,matlab中中图像PSNR和SSIM的计算
- vue中使用ECharts引入中国地图