C++基础(6) - 复合类型(下)

指针

1、指针概述

1.1 存储器和存储地址空间

  • 存储器:计算机的组成中,用来存储程序和数据,辅助 CPU 进行运算处理的重要部分。
    • 内存:内部存储器,暂存程序或数据,掉电丢失。如 SRAMDRAMDDR3DDR4
    • 外存:外部存储器,长时间保存程序或数据,掉电不丢失。ROMFLASH、硬盘、光盘;
  • 内存是沟通 CPU 和 硬盘的桥梁:
    • 暂时存放 CPU 中的运算数据;
    • 暂时存放与硬盘等外部存储器交换的数据;
  • 存储地址空间:对存储器编码的范围。
    • 编码:对每个物理存储单元(一个字节)分配一个号码;
    • 寻址:根据分配的号码找到相应的存储单元,完成数据的读写;

1.2 内存地址

  • 将内存抽象为一个很大的一维字符数组,通过编码为内存的每一个字节分配一个32位或64位的编号,这个内存编号就被称为内存地址(唯一);
  • 内存中的数据会被分配不同的地址个数。例如 char 占一个字节分配一个地址,int 占四个字节分配四个地址…
  • 对变量应用地址运算符 & 可以获取其在内存中的地址。例如对于变量 int num,则 &num 就是它的地址;

1.3 指针和指针变量

  • 指针:和 intdouble 等基础数据类型类似,是一种独立的数据类型,不同的前者存储的是实在的数据,但是指针这种类型存储的是指向这些数据的内存地址;
  • 指针变量:本质就是变量,只是该变量存储的是内存地址,而不是普通的数据。不同类型的指针变量所占用的存储单元长度是相同的;
  • 为什么要使用指针类型来保存地址值:
    • 编译时类型检查;
    • 指明一个内存地址所保存的二进制数据该怎么解释;

 

2、声明和初始化指针变量

2.1 指针变量的声明

数据类型 * 变量名;	int * num;
数据类型 *变量名;	int *num;
数据类型* 变量名;	int* num;

这里需要强调的是,数据类型* 才是指针变量的数据类型,如 int*double* 等等。而对于在哪里添加空格,对于编译器来说没有任何区别。

* 前面的类型是什么,*num 中就存储的是什么类型的数据。如 int* p,说明 *p 获取的是 int 型数据,但是存储地址的变量 p 的长度都是相同的,它取决于计算机系统。*p 这种形式的表示方法的意思之后会介绍。

注意:对每一个指针变量名,都需要使用一个 *

int* p1, p2;	// 声明创建一个指针变量p1和一个int类型变量p2
int *p1, *p2;	// 声明创建一个指针变量p1和一个指针变量p2

2.2 指针变量的初始化

  • 在声明语句中初始化指针变量:

    int num = 5;
    int *pt_num = #
    
  • 声明指针变量后对其进行初始化:

    double d = 2.0;
    double *pt_d;
    pt_d = &d;
    

指针变量

 

3、使用指针变量

3.1 解除引用

  • * 运算符称为间接值(indirect value)或解除引用运算符,将其应用于指针变量,可以得到该地址处存储的值;

  • 即假如 ptr 是一个指针变量,则 ptr 存储的是一个地址,而 *ptr 表示存储在该地址处的值;

  • 一定要在对指针变量应用解除引用之前,将指针变量初始化为一个确定的、适当的地址。因为在 C++ 中创建指针时,计算机将分配用来存储地址的内存,但不会分配用来存储指针所指向的数据的内存:

    long *ptr;
    *ptr = 22332223;	// 不能这样赋值!!
    

    若分配给 ptr 的地址中有重要的数据、甚至是系统值,此时给里面赋值,很可能就造成错误!

  • 不能简单的将整数赋值给指针变量,指针不是整型:

    // int *ptr = 0xN8000000;	// 错误!!
    
    int *ptr = (int *)0xB8000000;
    

3.2 野指针和空指针

  • 任意数值赋值给指针变量没有意义,这样的指针变量就是野指针,野指针指向的区域是未知的;
  • 野指针不会直接引发错误,操作野指针指向的内存区域可能会出问题;
  • 在 C++ 中,值为 0 的指针变量被称为空指针(null pointer)。空指针不会指向有效的数据;
  • 在 C++ 中,空指针的值可以用 0、NULL、nullptr 来展示;
  • C++ 提供了检测并处理内存分配失败的工具,后面再讨论;

 

4、指针的宽度和跨度

4.1 自身类型和指向类型

  • 指针变量的自身类型:去掉变量名,剩余的部分就是指针变量的自身类型,例如 int *int **
  • 指针变量的指向类型:去掉变量名和离它最近的一个 *,剩余的部分就是指针变量指向的类型,例如 intint *

4.2 指针变量所取内容的宽度

指针变量所取内容的宽度是由指针变量所指向的类型长度决定的。

例如 int *p1 = (int *)0x01020304;p1 指向 int 类型,所取内容宽度为 4short *p2 = (short *)0x01020304;p2 指向 short 类型,所取内容宽度为 2

4.3 指针变量加1的跨度

指针变量加1的跨度是由指针变量所指向的类型的大小决定。

例如,int 类型所占的字节数为4,那么它的指针变量加1,就会跨越4个地址。

 

5、指针数组

指针数组的本质是数组,数组中的每个元素都是指针类型。

int num1 = 1;
int num2 = 2;
int num3 = 3;
int num4 = 4;

int *arr[4];
arr[0] = &num1;
arr[1] = &num2;
arr[2] = &num3;
arr[3] = &num4;

// 另一种赋值
int *arr[4] = {&num1, &num2, &num3, &num4};

指针数组

 

6、二级指针

如果一个指针指向的是另外一个指针,称它为二级指针,或者指向指针的指针。

int num = 5;	// 内存中一块空间存储了5
int *p = #	// p存储了指向这块空间的地址,*p获取的是num的数值5
int **q = &p;	// q存储了指向p的内存地址,*q表示p中存储的地址值,**q获取的是num的值5

二级指针

 

7、数组的地址

一个数组的数组名被解释为其第一个元素的地址,而对数组名应用地址运算符时,得到的是整个数组的地址。

short tell[10];
cout << tell << endl;	// 0x61fe00;展示 &tell[0] 的地址
cout << (tell + 1) << endl;	// 0x61fe02;加了2
cout << &tell << endl;	// 0x61fe00;展示整个数组的地址
cout << (&tell + 1) << endl;	// 0x61fe14;因为十六进制,所以加20为0x14
cout << sizeof(tell) << endl;   // 20;将 sizeof 用于数组名,返回整个数组的长度(单位为字节)

虽然 tell&tell 的两个地址在结果上是一样的,但从概念上 &tell[0](即 tell) 是一个 2 字节内存块的地址,而 &tell 是一个 20 字节的内存块地址。因此 tell+1 将地址值加2,而 &tell + 1 将地址加20。

具体的 &tell 是怎么来的呢?语句如下:

short (*pas)[10] = &tell;

 

8、newdelete 运算符

8.1 使用 new 分配内存

  • C++ 中可以使用 new 运算符在运行阶段分配内存;

  • 动态分配内存的格式:

    typeName *pointer = new typeName;
    // 例如
    int *p = new int;
    
  • new 运算符返回该内存的地址;

  • 数据对象指的是为数据项分配的内存块;

  • 动态分配内存使程序在管理内存方面有更大的控制权;

  • new 分配的内存块通常与常规变量声明分配的内存块不同。变量的值都存储在栈内存(stack)区域中;而 new 运算符从堆(heap)或自由存储区(free store)的内存区域分配内存,所以最后一定要记得释放内存;

8.2 使用 delete 释放内存

  • delete 运算符可以在使用完内存后,将内存归还给内存池。归还或释放的内存可供程序的其他部分使用;

  • 使用 delete 时,后面要加上指向内存块的指针变量(内存块是由 new 分配):

    int *p = new int;
    ...
    delete p;
    

注意

  • 一定要配对使用 newdelete,斗则会发生内存泄漏(memory leak);
  • 不要释放已经释放的内存块;
  • 不能使用 delete 来释放声明变量所获得的内存;

 

9、动态数组的创建

9.1 使用 new 创建动态数组

  • 通常对于大型数据(如数组、字符串和结构),应使用 new 来分配;

  • 创建动态数组需要将数组的元素类型和元素个数数目告诉 new,格式如下:

    typeName *pointer = new typeName[num];
    // 例如
    int *p = new int[10];
    
  • new 运算符返回第一个元素的地址;

  • 通过指针变量 pointer 来操作数组;

  • 对于使用 new 创建的数组,应使用 delete [] 来释放:

    delete [] p;
    

9.2 使用 newdelete 的规则

  • 不要使用 delete 来释放不是 new 分配的内存;
  • 不要使用 delete 释放同一个内存块两次;
  • 如果使用 new [] 为数组分配内存,则应使用 delete [] 来释放;
  • 如果使用 new 为一个实体分配内存,则应使用 delete 来释放;
  • 对空指针应用 delete 是安全的(不会有任何事情发生);

 

10、动态结构体的创建

new 用于结构体由两步组成:创建动态结构体和访问其成员。

  1. 创建动态结构体的方式跟普通类型完全相同,格式如下:

    typeName *pointer = new typeName;
    // 例如:
    struct student {
    	int id;
        string name;
    };
    student *stu = new student;
    
  2. 访问动态结构体的成员:

    (*stu).id;
    // 或者
    stu->id;
    

 
 

C++ 内存管理

1、C++ 的内存划分

  • 栈区:由编译器自动分配与释放,存放为运行时的函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈;
  • 堆区:一般由程序员自动分配,如果程序员没有释放,程序结束时可能由系统回收;
  • 全局区(静态区):存放全局变量、静态数据。程序结束后由系统释放;
  • 常量区(文字常量区):存放常量值,如字符串常量。不允许修改,程序结束后由系统释放;
  • 代码区:存放函数体(类成员函数和全局区)的二进制代码;

 

2、C++ 管理数据内存的方式

C++ 根据用于分配内存的方法,有 3 种管理数据内存的方式:自动存储、静态存储、动态存储。这 3 种方式分配的数据对象存在时间的长短方面各不相同。

  1. 自动存储:在函数内部定义的常规变量使用自动存储空间,被称为自动变量。它们在所属的函数被调用时自动产生,在该函数结束时自动消亡。自动变量通常存储在栈内存中。

  2. 静态存储:静态存储是整个程序执行期间都存在的存储方式。使变量称为静态的方式有两种:一种是在函数外面定义它;另一种是在声明变量时使用关键字 static

    static int num = 10;
    
  3. 动态存储newdelete 运算符提供了一种比自动变量和静态变量更灵活的方法。它们管理了一个内存池,这在 C++ 种被称为自由存储空间。该内存池跟用于静态变量和自动变量的内存是分开的。在栈中,自动添加和删除机制使得占用的内存总是连续的,但 newdelete 的相互影响可能导致占用的自由存储区不连续,这使得跟踪新分配内存的位置更困难。

 

3、内存泄漏

如果使用 new 运算符在自由存储空间(或堆)上创建变量后,没有调用 delete,即使指针变量的内存被释放,但在自由存储空间上动态分配的内存也将继续存在。因为指向这些内存的指针无效,这样将无法访问自由存储空间中的这些内存,从而导致内存泄漏。

被泄露的内存在程序整个生命周期内都不可使用,因为这些内存被分配出去但无法收回。

内存泄漏可能会非常严重,以致于应用程序可用的内存被耗尽,导致程序崩溃。要避免内存泄漏,最好是养成同时使用 newdelete 运算符的习惯,在自由存储空间上动态分配内存,随后便释放它。

 
 

类型别名

C++ 为类型建立别名的方式有两种,一种是使用预处理器,另一种是使用 C++ 的关键字 typedef 来创建别名。

  1. 使用预处理器

    #define BYTE char
    

    预处理器在编译阶段将所有的 char 替换成 BYTE

  2. 使用关键字 typedef

    typedef typeName aliasName;	// 为 typeName 起了个别名 aliasName
    // 例如:
    typedef char byte;
    typedef char* byte_pointer;
    

由于预处理器的局限,例如 #define FLOAT_POINTER float*,将其作用在 FLOAT_POINTER pa, pb; 时,真实情况是 float *pa, pb;。它只会将 pa 定义成指针变量。

但是 typedef 方法不会出现上面的问题,它能够处理更复杂的类型别名。

注意typedef 不会创建新的类型,而是为已有的类型建议一个别名!

 
 

字符函数库 —— cctype

C++ 从 C 语言种继承了一个与字符相关的、非常方便的函数软件包,它可以简化诸如确定字符是否为大写字母、数字、标点符号等工作。在 C 语言中的老式风格为 ctype.h

函数名称返回值
isalnum()如果字符是字母或数字,返回true,否则false
isalpha()如果字符是字母,返回true
iscntrl()如果字符是控制字符,返回true
isdigit()如果字符是数字(0~9),返回true
isgraph()如果字符是除空格之外的打印字符,返回true
islower()如果字符是小写字母,返回true
isprint()如果字符是打印字符(包括空格),返回true
ispunct()如果字符是标点符号,返回true
isspace()如果字符是标准空白字符,如空格、进纸、换行符、回车、水平制表符、垂直制表符,返回true
isupper()如果字符是大写字母,返回true
isxdigit()如果字符是十六进制数字(0~9、a~f、A~F),返回true
tolower()如果字符为大写,返回其小写,否则返回本身
toupper()如果字符为小写,返回其大写,否则返回本身
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值