【黑马程序员匠心之作|C++教程】C++基础入门、通讯录管理系统

本文介绍了C++的基础知识,包括数据类型如整型、浮点型、字符型和布尔型,以及它们的内存占用。还讲解了运算符如算术运算符、比较运算符和逻辑运算符,以及如何使用指针访问和修改内存中的数据。接着讨论了函数的定义、调用和参数传递,特别是指针作为参数时如何修改实参的值。此外,文章还涵盖了结构体的定义和使用,包括结构体数组、结构体指针以及结构体在函数中的应用。最后,提到了数组可以通过值传递修改实参数组元素的值这一特殊情况。
摘要由CSDN通过智能技术生成

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++关键字冲突,产生歧义


image-20230614205506904

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; //2
    cout << "short : "<< sizeof(num1) << endl; //2
    
    int num2 = 10;
    cout << "int : "<< sizeof(num2) << endl; //4
    return 0;
}
// 所占字节大小: short < int <= long <= long long

2.3 实型(浮点型)

作用:表示小数

3.14 三位有效数字,包含小数点前和小数点后的长度
在浮点数表示中,有效数字是指能够精确表示的数字位数,包括小数点前和小数点后的位数。

对于 float 类型:
它使用32位(4字节)来表示浮点数,其中23位用于表示尾数,所以它可以提供大约6-7位的有效数字。

对于 double 类型:
它使用64位(8字节)来表示浮点数,其中52位用于表示尾数,所以它可以提供大约15-16位的有效数字。

数据类型占用空间有效数字范围
float 单精度4 字节 32bit7位有效数字
double 双精度8 字节 61bit15 ~ 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:


image-20230614205506904

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字符


image-20230614205506904

// 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 >> 数据

cincout对象通过重载各个数据类型的输入和输出运算符来实现对不同数据类型的输入和输出

cin 是用于从标准输入设备(通常是键盘)读取数据的对象。它通过重载了输入运算符 >> 来支持不同数据类型的输入。例如,可以使用 cin >> num; 从标准输入读取一个整数并将其存储到变量 num 中。

cout 是用于向标准输出设备(通常是屏幕)输出数据的对象。它通过重载了输出运算符 << 来支持不同数据类型的输出。例如,可以使用 cout << "Hello, World!"; 将字符串 “Hello, World!” 输出到标准输出。

3 运算符

作用:用于执行代码的运算

  • 算数运算符:处理四则运算
image-20230614223730454

取模 % :

两个数相除,除数不可以位0 所以也做不了取模运算

int a = 10, b = 0;
// cout << a % b << endl; //报错

两个小数,不可以做取模运算

double d1 = 3.14, d2 = 1.1;
//cout << d1 % d2 << endl; //报错

只有整形变量才可以进行取模运算

前置和后置:

本质上是让变量+1,但是区别:

前置递增:先让变量+1,然后再进行表达式运算

后置递增:先进行表达式运算,再让变量+1

​ 前置递减和后置递减同理

  • 赋值运算符:将表达式的值赋给变量
    image-20230614223730454

  • 比较运算符:用于表达式比较,并返回 真或者假
    image-20230614223730454

  • 逻辑运算符:用于根据表达式的值返回 真或者假
    image-20230614223730454

    逻辑非

    int a = 10;
    cout << !a << endl; // 0  在C++中,除了0都是真
    cout << !!a << endl; // 1
    

    逻辑与:同真为真,其余为假

    逻辑或:同假为假,其余为真

4 程序流程结构

C / C++ 支持最基本的三种运行结构:顺序结构,选择结构,循环结构

  • 顺序结构:程序按照顺序执行,不发生跳转
  • 选择结构:依照条件是否满足,有选择的 执行相应的功能
    • if,三目运算符,switch
  • 循环结构:依照条件是否满足,循环多次执行某段代码
    • whiledo ... whilefor

4.1 选择结构

  • if 语句

    • 单行格式 if(条件) { 条件满足执行的语句 }


      image-20230614205506904

      • 注意if条件后面加上;,则{}和if条件分离,{}内无论满足或不满足都会执行

        if(score >= 90); { cout << A << endl; }

    • 多行格式

      if(条件) { 
          ... //条件满足执行的语句;
      }  else { 
          ... //条件不满足执行的语句 
      }
      
      image-20230614205506904
    • 多条件

      if(条件1){
          ... // 条件1满足执行的语句;
      }else if(条件2){
          ... // 条件2满足执行的语句;
      }else{
          ...// 都不满足执行的语句
      }
      
      image-20230614205506904
    • 嵌套 筛选更加精准

      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(循环条件) { 执行语句; } 只要循环条件结果为真,就执行循环语句

    image-20230614205506904
    // 输出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(循环条件); 先执行一次循环体,再判断循环条件

    image-20230614205506904
    // 输出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 0
    
    int 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. 比较相邻元素,如果第一个元素比第二个元素大,就交换它们两个
    2. 对每一对相邻元素做相同工作,执行完毕后,找到第一个最大值
    3. 重复以上的步骤,每次比较次数-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个步骤:

  1. 返回值类型
  2. 函数名
  3. 参数列表
  4. 函数体
  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 函数的分文件编写

作用:让代码结构更加清晰

函数分文件编写一般四个步骤:

  1. 创建后缀名 为 .h 的头文件
  2. 创建后缀名为 .cpp 的源文件
  3. 在头文件中写函数的声明 .h
  4. 在源文件中写函数的定义 .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; // 1

int *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 = 20
    swap1(&i, &j); // 地址传递
    printf("After swap1(), i = %d, j = %d\n", i , j);// After swap1(), i = 20, j = 10
    return 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 在定义结构体时顺便创建结构体变量 st3
    st1.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;
}

编译器在 agescore 之间插入了 1 字节的填充,以保证对齐。因此sizeof(Student) 的结果为 8 字节

编译器在 agescore 之间插入了 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] 40
        int 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;
}
### 回答1: 第1阶段c 匠心之作从0到1入门是一个学习计算机编程语言C的项目。这个项目适合初学者,让他们从零开始学习C语言,逐步建立起对编程的基本理解和技能。 在这个项目中,学习者将了解C语言的基本语法和数据类型,学习如何在C语言中编写简单的程序。他们将学会如何使用变量和常量,在程序中存储和操作数据。他们还将学习如何使用条件语句和循环语句,来控制程序的流程和执行。 此外,学习者还将了解函数和数组的概念和用法。他们将学会如何定义和调用函数,以及如何使用数组来存储和处理大量的数据。他们还将学习如何使用指针,在程序中访问和操作内存中的数据。 在这个项目的最后阶段,学习者将有机会使用所学的知识,编写一些简单但有意义的小项目。这些项目可以是简单的计算器,猜数字游戏或其他有趣的应用。通过实践,学习者将能够巩固他们在C语言编程方面的基础,并培养他们解决问题和思考编程逻辑的能力。 总的来说,第1阶段c 匠心之作从0到1入门是一个系统化的学习计划,旨在帮助学习者从零开始学习C语言编程。通过这个项目,学习者将建立起对编程的基本理解和技能,并能够编写一些简单但有意义的小项目。这个项目不仅有助于学习者进一步提高他们的编程能力,也可以为他们日后学习更高级的编程语言奠定坚实的基础。 ### 回答2: 首先,第1阶段的c编程可以被称为“匠心之作”,因为它是一个从零到一的入门级课程。在这个阶段,学习者将掌握c编程的基础知识和技能。 在这门课程中,学习者将从基本概念开始学习c编程。他们将了解变量、数据类型、运算符等基本概念,并学习如何使用它们来解决问题。此外,学习者还将学习如何使用控制结构,如条件语句和循环语句,来控制程序的执行流程。 在学习这门课程的过程中,学习者将通过一系列实践项目来巩固他们的理论知识。他们将编写简单的程序来实现一些基本功能,如计算器、猜数字游戏等。通过这些项目,学习者将学会如何将复杂的问题分解为更小的子问题,并将它们逐步解决。 此外,学习者还将学习如何调试他们的程序。他们将学会如何使用调试工具来找出程序中的错误,并学习一些常见的错误类型和解决方法。这将帮助他们提高他们的编程技能,并培养他们解决问题的能力。 总之,第1阶段的c编程课程可以被称为“匠心之作”,因为它为学习者提供了一个从零到一的入门级课程。通过学习这门课程,学习者将掌握c编程的基本概念和技能,并培养他们的问题解决能力。这将为他们进一步学习更高级的c编程提供坚实的基础。 ### 回答3: 第1阶段的c课程是一个精心设计的从零开始的入门课程,旨在帮助学习者从无到有地掌握c编程语言。这门课程是一项工匠式的作品,注重细节和实践,为学习者提供了一个全面而系统的学习经验。 这门课程的教学团队投入了大量精力和心血,以确保学习者可以获得最佳的学习效果。课程内容经过精心规划和组织,从基本的语法和概念开始,逐步引导学习者掌握c语言的各个方面。通过丰富的教学材料、实例和练习,学习者可以深入了解c语言的特性和用法。 除了理论学习,这门课程还注重实践。学习者将有机会参与到各种编程项目中,通过编写实际的c代码来巩固所学知识。这不仅有助于理解抽象的概念,还可以提高学习者的编程能力和自信心。 与传统的教学方式不同,c第1阶段课程采用了多种教学方法和学习资源。学习者可以通过在线论坛与教学团队和其他学习者交流和分享,获得更多的互动和反馈。此外,课程还提供了丰富的学习支持和辅导,帮助学习者解决问题和提高学习效率。 这门c第1阶段课程旨在帮助学习者从零开始学习c语言,了解编程的基础原理和技巧。它是教学团队用心打造的一项匠心之作,旨在培养学习者的编程能力和创造力。无论是对于编程新手还是有一定经验的学习者来说,这门课程都是一个宝贵的学习资源,能够帮助他们快速入门并掌握c语言的精髓。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值