4. 复合类型

100 阅读13分钟

4. 复合类型

[TOC]

数组

用以存储同类型的多个值。声明数组应指出三个点:存储值的类型、数组的名称,要存储的元素数。

声明数组的大小必须为整型常数或者以const修饰的变量

// arraySize 必须为整型常数或者以const修饰的变量
// 创建了一个typeName数组
typeName arrayName [arraySize];
  1. 初始化数组的方法

    1. 数组只有在声明语句中才能初始化数组元素(然后声明语句就变成了定义语句)

    2. 数组可以使用列表初始化的方法来声明

      // 大括号,元素之间使用逗号隔开
      // 如果进行部分元素的初始化,那么其他元素将会被设置为0
      int yamcosts[3] = {20, 30, 5};
      float plts [] {51.511, 8482.55, 55.0}; // 省略 = 
      float balances [] {};  // 空列表会将元素全部设置为0
      int yam [] = {33, 22, 54};  // 编译器根据列表内元素个数计算数组大小/*
      列表初始化禁止缩窄转换(大类型到小类型的转换)
      Q: 什么是大类型到小类型的转换?
      A:会造成数据精度丢失和范围溢出的类型转换。
      */
      long plifs[] = {25, 92, 3.0};  // 不被允许,浮点-》long int 会造成精度丢失
      char slifts[] = {'h', 'l', 112211, '\0'};  // 不被允许, 会造成范围溢出,char 可放不了112211这么大的数字。
      char tlifts[] = {'h', 'l', 112, '\0'};  // 允许, 112没有超过char的范围
      
    3. sizeof 用于数组名,将会返回整个数组的长度,单位为byte

C风格字符串

  1. 定义:以空字符结尾的一系列字符。这也是字符数组和字符串之间的区别
char dog[8] = {'b', 'e', 'a', 'u', 'x', ' ', 'i', 'i'};  // 不是字符串,是存有字符常量的数组
char cat[8] = {'b', 'e', 'a', 'u', 'x', ' ', 'i', '\0'};  // 字符串, 以'\0'结尾,其ASCII码为0
  1. 字符串初始化的方式

    1. 使用上面的列表加上单引号

    2. 使用双引号包裹字符串,他会隐式的为字符串添加结尾空字符。

      char bird[11] = "Mr. Cheeps";  // 使用确定大小的数组时记得为结尾空字符预留出空间。
      char fish[] = "Bubbles";  // 占8个字节, 显式7, 隐式1
      
    3. 'S' 和"S"的区别

      /*
      'S' 是字符常量S,是83的另一种写法
      "S"是字符串,它由两个字符组成'S''\0'"S"实际代表的是字符串的首地址
      */
      
    4. 字符串的自动拼接

      两个字符串之间如果是通过空白(空格, 制表符, 换行符)分割的,那么两个字符串将会被自动拼接成一个

      // 这是一个字符串,第二行的首字母会代替第一行的'\0'
      cout << "aidjiaojd"
          "adjoiajd jdaid";
      
    5. sizeof运算符作用于字符串时,即是于作用于字符数组,会指出所占的全部空间,包括'\0'。strlen()则会返回显式长度,而不是数组的长度

    6. 在字符数组中插入'\0'将会截断字符数组。

      const int kArSize = 15;
      char name2[kArSize] = "c++owboy";  // 此时name2代表的字符串为"c++owboy\0"
      ​
      name2[3] = '\0';  // 此时name2代表 "C++\0", '\0'将会截断字符数组
      ​
      
    7. 如何进行字符串输入?

      1. 面向单词的输入

        1. cin >> namecin使用空白(空格,换行,制表符)来确定字符串的结束位置
      2. 面向行的输入

        1. cin.get()方法。不自动舍弃换行符

          // 单个字符
          char ch;
          cin.get(ch);  // 或 ch = cin.get();但别用,省的混乱// 获取一行字符串,或遇到换行符结束
          char name[20];
          cin.get(name, 19);  // 接收一行字符,且最大19个字符// 舍弃输入流中不需要的字符,或回车
          // cin.get()方法不会自动获取这一行剩下的(注意是剩下的),那么就需要手动丢弃,以免影响下一次输入
          cin.get();  // 无参数方法,用以丢弃剩余输入
          
        2. cin.getline()方法。 自动舍弃换行符

          // 获取一行字符串
          char str[100];
          cin.getline(str, 100);  // 读一行,最大99个字符,或遇到换行符结束
          
          // 指定定界符的获取行
          char str[100];
          cin.getline(str, 100, '\t');  // 读取一行,最大99,或遇到换行符,或遇到'\t'结束本行。
          
        3. .getline(), .get()方法的问题

          1. 当读取空行时,输入被阻断怎么办
          2. 当输入行包含的字符数比指定的多,两种方法会将余下的字符留在输入队列中,这些应该怎么办?
        4. 一段c字符串输入程序

          #include <iostream>
          #include <limits>
          using namespace std;
          
          using namespace std;
          
          int main()
          {
              const int kMaxSize = 10;
              char input[kMaxSize];
              char input1[kMaxSize];
          
              // 提示用户输入并读取
              cout << "Enter a string: "  << endl;
              cin.getline(input, kMaxSize);
          
              // 检查是否需要清除输入缓冲区
              // cin.gcount() 获取.getline()读取的字符数
              if (cin.gcount() == kMaxSize - 1) { 
                  cin.clear(); // 清除错误状态位
                  cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 清除输入缓冲区
              }
              
              // 进行下一次输入
              cout << "Enter a string: "  << endl;
              cin.getline(input1, kMaxSize);
          
              cout << "input: " << input  << endl;
              cout << "input1: " << input1  << endl;
          
              return 0;
          }
          
    8. 数组的复制、附加,求长度

      #include <cstring>
      
      char charr1[20];
      char charr2[20];
      char charr3[20];
      
      strcpy(charr1, charr2);  // 复制
      strcat(charr1, charr2);  // 附加到末尾
      strlen(charr3);  // 求字符串长度
      

string类字符串

  1. getline()函数输入字符串

    string str;
    getline(cin, str);  // 将输入存入str中
    
  2. 支持使用c风格字符串初始化。

  3. 支持赋值、拼接、附加,求长度

    string str1;
    string str2;
    string str3;
    
    str1 = str2;  // 赋值
    str3 = str1 + str2;  // 拼接
    str2 += str1;  // 附加
    strlen(str1);  // 求长度
    str2.size();  // 求长度
    

结构体、共用体

结构体

struct StructName
{
    TypeName name;
    ...
}

struct infla
{
    char name[20];
    float volume;
    double price = 50.0;
}
  1. 可以使用列表来初始化结构体,依旧要符合列表初始化的规定,不能缩窄

    infla stc1{ "adnnak", 20, 33.0};  // 列表初始化,里面是元素,而不是语句,所以是,而不是;
    
  2. 结构体可以赋值

    infla in1, in2;
    in1 = in2;  // 赋值被允许
    
  3. 结构数组

    可以通过数组的形式创建一系列的结构体

    infla inflas[10];  // 创建了10个结构体,并且是一个数组
    
    // 并且可以通过列表初始化的方法来对其进行初始化。
    infla inflas[10] = 
    {
        {"adnnak", 20, 33.0},
        {"aak", 33, 23.0},
        ...
    }
    

共用体

union UnionName
{
    TypeName value_name;
    ...
}

只能同时存储一种类型的数据。

用于当数据项使用两种或两种及以上的数据格式时(但不会同时使用),可节省空间。例如一个商品的ID有的为整数,有的为字符串,在这种情况下,不需要再设置其他的变量。union所占空间为其最大成员的长度。

例:

struct Widget
{
    string brand;
    int type;
    union id
    {
        long id_num;
        char id_char[20];
    };
}

widget prize;
...
if(prize.type == 1)
    cin >> prize.id_num;
else
    cin.getline(prize.id_char, 20);

我想,是为了让程序规整? 省的对同一类型的物品所设置的变量有的有,有的没有。但没什么吊用我的评价是。

枚举

一种代替const创建符号常量的方式

enum spectrum {red, orange, yellow};  
// 这将完成两件事情:
//	使spectrum 称为新类型名称,被称为枚举
//	其成员作为符号常量,对应整数值0~2,称为枚举量
// 	spectrum 只有3个可能值, 其他值非法
  1. 枚举量是整型,可被提升为int,但int不能自动转换为枚举类型,可以通过强制类型转换。

    int color = red;  // 被允许, blue自动转化为int
    band = 2;  // 不行,int不能自动转化为spectrum,应该是为了防止将非法的值赋给它
    band = spectrum(2);  // 可以通过强制类型转换
    color = 1 + red;  // allow red被转化为int
    
  2. 顺序排列,从0递增。但能够通过定义枚举时赋值的方式,显式初始化枚举类型。

  3. 枚举的取值范围为

    上限: 枚举量比最大值大的最小二的幂-1。 如 最大值为101,最小二的幂为128,那么上限为127; 下限: 枚举量全部不小于0,则下限为0; 枚举量有小于0的,则下限为找上限的相同方式,但加上负号。如最小为-6,绝对值为6,最小二的幂8,减一7,加负号为-7。

    why:

    在节省空间的同时确保其取值范围覆盖了枚举常量的所有可能值。

指针

image-20240502163344358

常规指针

Q: 什么是指针?

A: 指针是一种变量,但与常规变量不同的是,其存储(指向)了一个地址,代表了在内存中的某一块区域,而这个地址中可以存放值。

&为寻址运算符,对常规变量使用可以用来获取其地址。

*为解除引用运算符,对指针变量使用,可以取出在其”值”中存储的值。引用"在这里表示指针所指向的内存地址,而"解除"表示对该内存地址进行操作以获取其存储的值。

int *p1;  // 我觉得还是这种比较不容易弄混
int* p2;  // 好用不了一点
int* p3, p4;  // 这是声明了一个int指针和一个int变量

// 在声明时初始化指针
int higgens = 5;
int *pt = &higgens;  // 在这种情况下,初始化的是指针pt,而不是它指向内存中存储的值。也就是说,这是将pt的值设置为&higgens
  1. 在对指针使用接触引用运算符之前,必须对指针进行初始化,(对常规变量进行初始化即给他一个值,对指针变量进行初始化即给他一个地址。)

    Q:如何理解?

    A: 不妨思考常规变量在没赋值之前能用吗?里面是个随机数,用不了一点。指针也是相同啊,对指针进行初始化,等于将一块内存地址赋给了它,那么他就有值了,就可以用了。但指针可以用,指针指向地址内存放的数据可还不能用啊, 除非指向地址内存内的值你知道。

    long *fell;
    *fell = 23333;  // 这种就是错误的,因为仅声明了一个指针,但它并没有定义它指向哪里,那么他是随机的(指向哪里都有可能)。对其解除引用进行赋值,可能就会发生危险(比如它指向具有特定功能的内存中)
    

new指针

// 声明一个new指针
// 需要在两个地方指出数据类型:声明什么样的的指针 和 指定需要什么样的内存
typeName *pointer_name = new typeName;  

// 使用new声明一个int 指针
int p_int = new int;
  1. 实现在运行阶段为指针分配内存。
  2. 需要与delete配合使用,等待不需要此变量时,释放相应的内存。否则会发生内存泄漏(就是new完没delete)。

使用new来创建动态数组

Q: 什么叫动态数组?

A:对于数组而言,存在静态数组与动态数组,其所对应的又是new数组的静态联编和动态联编概念。如果在编译时就给数组分配内存,则称为静态联编;使用new来声明数组,则在运行阶段给数组分配内存,称为动态联编。静态数组无论需不需要,只要声明,那么内存一定被分配。但是动态数组会根据运行阶段是否需要来决定创建与否和其长短。

静态联编和动态联编出现在面向对象编程中,静态联编通常用于非虚函数,在编译时就确定了方法调用的具体实现,这意味着函数调用时编译器会直接调用该方法。而动态联编是在运行阶段,根据对象的类型确定方法调用的具体实现。常用于虚函数根据指针或引用所指向的对象的实际类型在运行阶段来确定方法的调用。

// 指针类型、元素类型、元素数目
int *pp = new int [10];  // 数目大小必须指定,在堆上动态分配内存需要知道内存大小
delete [] pp;  // 一一对应

Q:使用指针创建数组后,指针名和数组名之间的差别:

A:指针是一个变量,指针首先指向的是第一个地址,但是可以进行算术运算(加法和减法)

而数组名在编译时会转换为指向第一个元素的常量指针,即数组名被视为数组的第一个元素的地址,但是不能进行算术运算(+1就会跨越整个数组大小的字节数)。

int * p_arr = new int [12];
int arr[12];

p_arr[0];  // 被允许使用[]取值
p_arr = p_arr + 1; // 指针算术运算,被允许,本来指向第一个地址,现在指向了第二个 每次移动一数据类型的字节
p_arr[0];  // 此时的[0] 相当于没进行指针+1之前的[1]

arr[0];  // 被允许
arr = arr + 1;  // 不被允许,它更类似于一个常量指针
// 括号的优先级最高
short *pas[10];  // 声明了一个数组,里面有十个指针
short (*pas)[10];  // 声明了一个指针,指向一个数组
// 也可以使用new后开辟空间
// 在声明时开辟
char *pc = new char;

// 先声明,后开辟空间
char *pa;
pa = new char;  

指针的算术运算

指针可以与整数相加。加1的结果等于原来的地址加上指向对象所占用的总字节数。

两指针可以相减。仅当指针指向同一个数组时,指针相减得到一个整数,这是两个元素的间隔(不是字节数)

int tacos[10] = {5, 2, 3, 4, 6, 8, 0};
int *pt = tacos;  // 不需要取地址,因为数组名就代表第一个地址
int *pe = &tacos[9];  // 这里需要取地址,带上[]就代表的取元素。
int count = pe - pe;  // count = 9 - 0 = 9

cout和字符串

C++ 中输出数组分为两种情况,字符型数组和非字符型数组。

当定义变量为字符型数组时,cout<<数组名时,系统会将数组当作字符串输出,从而打印字符串直到一个null

cout <<其他类型的数组时,系统将会输出数组第一个元素的地址。(数组名时第一个元素的地址)。

总之有三点原则:

  • 数组名是第一个元素的地址,这一点不会变
  • 当给cout <<提供一个非字符类型的地址,那么它将输出这个地址。
  • 当给cout << 提供一个字符的地址,则他将从该字符开始输出,直到遇到空字符为止。 因为ostream对 << 进行了重载,
#include <iostream>

int main() {
    
    // cout << 字符数组
    char str[] = "Hello, world!";
    
    std::cout << str; // 输出整个字符串Hello, world!
    std::cout << &str[7];  // 输出world!, 因为给的是字符地址
    std::cout << str[7];  // 输出字符`w`

    // cout << 字符
    char ch = 'a'  ;
    std::cout << &ch << std::endl;  // 输出axxxxx,xxx为不确定,直到碰到第一个null
    std::cout << ch << std::endl;  //  输出字符`a`
    
    // cout << 非字符类型
    int a[10]={1,2,3};
	std::cout << a <<endl ; //按16进制输出a的值(地址)
    return 0;
}

动态结构

// 创建一个inflatable动态结构
inflatable *ps = new inflatable;

但是这种动态结构他没有名称,只有一个指针指针内存储了结构所在的地址看本节开头,new的没名字。)。对于这种没有名称,只有地址的成员,使用箭头运算符来访问其成员。

ps->price;
ps->volume;

ref