C++学习笔记(二)

这篇博客详细讲解了C++中的数据类型,包括内存、补码运算规则、数据类型定义及转换。接着探讨了进程空间的结构,如进程、程序、内存区域和数据存储。在数组部分,介绍了数组的本质、访问和类型转化,特别是二维数组和数组指针的使用。最后,文章阐述了指针的概念,包括一级指针、二级指针的使用和类型转化,以及指针在数组和函数中的应用。
摘要由CSDN通过智能技术生成

文章目录


C++学习的第二章,这里对之前学过的内容进一步的提升

第一章 数据类型

1.1 内存

内存是线性的,最小储存单位为比特 (Bit) ,是按字节(Byte)为单位进行编址的,以32位机为例,编制形式如下:
在这里插入图片描述

1.2 补码

1.2.1 运算规则

补码的运算规则是互逆的,补码其实就是二进制的一种编码方式,一种编码规则,凡是编码规则的存在,就必定带来一定的取值范围,取值范围的存在就会影响后面的运算,也影响了数据类型的范围。
在这里插入图片描述
补码的一大特点就是,能使加减乘除运算一并使用加法表示。

1.3 数据类型

1.3.1 数据类型

在这里插入图片描述

1.3.2 数据类型是对内存的格式化

数据类型是对内存的格式化,这一句话很重要。数据类型提供了,申请内存单元的大小和访问规则。数据类型是架设在线性内存 上的一种逻辑关系。什么是格式化呢,就是先给它一定的格式,再去储存访问它。

1.4 类型转化

1.4.1 类型转化的原理

1.4.1.1 小数据赋给大变量

不会造成数据的丢失,系统为了保证数据的完整性,还提供了符号扩充行为。这方面之前举过一个很不恰当的例子:小运载卡车将货物放到大运载卡车上,示意图如下:
在这里插入图片描述

1.4.1.2 大数据赋给小变量

会发生 Truncate(截断行为),有可能会造成数据丢失。

int main(int argc, char * argv[])
{
   
    int a = 0x5334fc;
    short b = a;
    cout << a << endl;
    cout << 0x34fc << endl;
    cout << b << endl;

    system("pause");
    return 0;
}

大数据赋给小变量时,相当于先将一个short类型大小的内存从最低位开始套在大数据上,然后截断。

在这里插入图片描述

1.4.2 隐式转化

1.4.2.1.整型提升

在 32 位机中,所有位为低于 32 的整型数据,在运算过程中先要转化为 32 位的 整型数据,然后才参与运算。

1.4.2.2 混合提升

引用一段来自KR的话:

  • First, if either operand is long double, the other is converted to long double.
  • Otherwise, if either operand is double, the other is converted to double.
  • Otherwise, if either operand is float, the other is converted to float.
  • Otherwise, the integral promotions are performed on both operands;
  • Otherwise, if either operand is unsigned int, the other is converted to unsigned int.
  • Otherwise, both operands have type int
int main(int argc, char * argv[])
{
   
    unsigned int a = 1;
    int b = -100;
    printf("a + b = %u\n", a + b);
    printf("a + b = %d\n", a + b);

    system("pause");
    return 0;
}

下面一道关于混合提升的练习题:

int main(int argc, char * argv[])
{
   
    char a[1000];
    int i;
    for (int i = 0; i < 1000; i++)
    {
   
        a[i] = -1 - i;
    }
    
    cout << strlen(a) << endl;

    system("pause");
    return 0;
}

以上代码的输出结果是多少?
之前有说过,二进制也是使用的模的系统,就像时钟一样,从1点一直走,走一圈就到了2点。
1111 1111开始走,走到下一个1111 1111,刚好就是一圈。所以会输出255.

第二章 进程空间(Process Space)

2.1 进程空间

在这里插入图片描述

  • command-line arguments and environment variables :放置了命令行参数与环境变量(环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数)。
  • stack:Auto类型的变量都在这里
  • heap:也就堆,存放的是成员变量,方法之外,类之内,随着对象而产生,随对象销毁而销毁。
  • uninitialized data(bss):未初始化数据段
  • initialized data:已初始化数据段
  • text:是存放了程序代码的数据:在代码段中,也有可能包含一些只读的常数变量,列如字符串常量等

2.2 进程/程序

2.2.1 程序

源文件经编译器,编译后的可执行性文件。程序是一个静态的概念,程序中包含 2 个区域,分别是text / initial data。

2.2.2 进程

进程,被操作系统加载至运行结束的过程。进程是一个动态的概念。进程包含 5 个区域,分别是:text / initial data / uninitial data / heap / stack

2.2.3 进程到程序

在这里插入图片描述
在这里插入图片描述

2.3 数据在进程空间的存储

2.3.1 示意图

在这里插入图片描述

2.3.2 数据在进程空间

在这里插入图片描述

2.4 压栈与出栈

要理解压栈与出栈,借助递归可以更好地理解,下面代码使用低轨倒叙打印了一个数组:

void ReverseArr(int* parr, int i, int len)
{
   
    if (i != len - 1)
    {
   
        ReverseArr(parr, i + 1, len);
    }
    cout << parr[i] << endl;
}

int main()
{
   
    int arr[10] = {
    1,2,3,4,5,6,7,8,9,10 };
    ReverseArr(arr, 0, 10);

    system("pause");
    return 0;
}

但是,同样的代码,换一下顺序就可以正序打印:

void ReverseArr(int* parr, int i, int len)
{
   
    cout << parr[i] << endl;
    if (i != len - 1)
    {
   
        ReverseArr(parr, i + 1, len);
    }
}

int main()
{
   
    int arr[10] = {
    1,2,3,4,5,6,7,8,9,10 };
    ReverseArr(arr, 0, 10);

    system("pause");
    return 0;
}

第三章 数组

3.1 一维数组

3.1.1 本质

数组是用于存储相同数据类型数据,且在内存空间连续的一种数据结构类型,他也是构造类型之一,数组的三要素有:

  • 范围
  • 步长
  • 起始地址

在这里插入图片描述

3.1.2 访问

数组名是数组的唯一标识符,数组的每一个元素都是没有名字的。

3.1.3 做参数传递

做参数传递,旨在传递三要素。

void PrintArr(int* arr, int len)
{
   
    for (int i = 0; i < len; i++)
    {
   
        cout << *(arr + i) << endl;
    }
}

int main(int argc, char* argv[])
{
   
    int arr[4] = {
    11,22,33,44 };
    PrintArr(arr, 4);

    system("pause");
    return 0;
}

arr 即代表了起始地址,又代表了步长,len则代表了范围。

3.2 二维数组

3.2.1 本质

二维数组的本质是一维数组,只不过,一维数组的成员又是一个一维数组而己。
三要素:

type name[M][N] = type[N] name[M]

3.2.2 初始化

行可以省略,列不可以初始化

3.2.3 访问

数组名依然是数组的 唯一 标识符。

int arr[3][4] = {
    0,0,0,0,
                  1,1,1,1,
                  2,2,2,2 };

// TODO

之前我们一直说 * 是取内容, & 是取地址。其实,这种理解并不全面。随后会进行这方面的补充.

3.2.4 线性存储

二维数组在逻辑上是二维的,但是在存储上却是一维的。正是这个特点,也可以用一维数组的方式去访问二维数组的,如下:

//二维逻辑
int main()
{
   
    int arr[3][4] = {
    0,00,0,0,
                      1,1,1,1,
                      2,2,2,2 };

    for (int i = 0; i < 3; i++)
    {
   
        for (int j = 0; j < 4; j++)
        {
   
            cout << arr[i][j];
        }
        putchar(10);
    }
     
    system("pause");
    return 0;
}

我们使用一维逻辑去实现:

int main()
{
   
    int arr[3][4] = {
    0,00,0,0,
                      1,1,1,1,
                      2,2,2,2 };

    int* p = (int*)arr;
    for (int i = 0; i < 12; i++)
    {
   
        cout << p[i] << endl;
    }
    
    system("pause");
    return 0;
}

以上两种都是一样的。

3.3 数组指针

3.3.1 引入

一次可以移动一个字节指针,称为 char * 指针,一次可以移动两个字节的指针,称为 short * 指针。
一次可以移动一格数组大小的指针,是什么类型的指针,又该如何称呼它呢?比如二维数组名:arr[3][4]; arr+1 一次加1的大小,就是 Int[4] 类型的大小, 即一个数组的大小。

3.3.2 定义

int [N] *pName; = > int( *pName)[N];

3.3.3 typedef

int main(int argc, char* argv[])
{
   
    typedef int(*AP)[4];

    AP ap = NULL;

    system("pause");
    return 0;
}

3.3.4 做参数传递

二维数组本质就是数组指针,所以跟其对应的形参也应该是数组指针类型的变量:

void PrintTwoDie(int(*p)[4], int len)
{
   
    for (int i = 0; i < 3; i++)
    {
   
        for (int j = 0; j < 4; j++)
        {
   
            cout << p[i][j];
        }
        putchar(10);
    }
}

int main()
{
   
    int arr[3][4] = {
    0,00,0,0,
                      1,1,1,1,
                      2,2,2,2 };
    PrintTwoDie(arr, 3);
    
    system("pause");
    return 0;
}

3.3.5 一维空间的二维访问

由于内存是线性的,所以我们可以将其在逻辑上使用二维逻辑。

// 一维空间的二维访问

int main()
{
   
    int arr[12] = {
    1,1,1,1,2,2,2,2,3,3,3,3 };

    int(*p)[4] = (int(*)[4])arr;

    for (int i = 0; i < 3; i++)
    {
   
        for (int j = 0; j < 4; j++)
        {
   
            cout << p[i][j] << endl;
        }
    }

    system("pause");
    return 0;
}

改成2行6列:

int main()
{
   
    int arr[12] = {
    1,1,1,1,2,2,2,2,3,3,3,3 };

    int(*p)[2] = (int(*)[2])arr;

    for (int i = 0; i < sizeof(arr)/sizeof(int[2]); i++)
    {
   
        for (int j = 0; j < 2; j++)
        {
   
            cout << p[i][j] << " ";
        }
        cout << endl;
    }

    system("pause");
    return 0;
}

第四章 指针

4.1 内存编址与变量地址

在这里插入图片描述
看一下下面的代码:

#include <iostream>

using namespace std;

int main(int argc, char* argv[])
{
   
    int data = 0x12345678;
    int *ptr_data = &data;

    printf("%p \n", &data);
    printf("%p \n", ptr_data);
    printf("%p \n", *ptr_data);

    system("pause");
    return 0;
}

输出如下:
在这里插入图片描述
所以可以总结出,指针的本质就是有类型的地址
那么上面的 int * ptr_add ,一句废话都没有,总共包括了以下两个方面:

  • 声明指针,指定大小( * ptr_add );
  • 类型决定寻址能力( int );

还有一点需要注意的是,声明那里的 * ,与 下面输出打印的 * 是不一样的。上面是用来声明指针类型, 而下面则是用来解引用的。

4.2 二级指针

二级指针,是一种指向指针的指针。我们可以通过它实现间接访问数据,和改变一级指针的指向问题。
什么是指向呢,之前也探讨过,指向实际就是保存了它的地址。
在这里插入图片描述

4.2.1 定义与初始化

char* *pt = &ch; * 号的位置应该严格按照这种方式去理解。pt 声明了这是一个一级指针,而char 则反映了它的类型。

4.2.2 对数据空间的间接访问

int main(int argc, char* argv[])
{
   
    char ch = 'a', ch2 = 'n';
    char* pc = &ch;

    char* *pt = &pc;

    printf("*pt = %c\n", **pt);

    system("pause");
    return 0;
}

4.2.3 改变一级指针的指向问题

int main(int argc, char* argv[])
{
   
    char ch = 'a', ch2 = 'n';
    char* pc = &ch;

    char* *pt = &pc;
    *pt = &ch2;

    printf("*pt = %c\n", **pt);

    system("pause");
    return 0;
}

通过二级指针,改变一级指针的指向问题,也相当于在探讨,通过二级指针,对一级指针进行初始化的问题。

4.3.3 初始化一级指针

4.3.3.1 返回错误信息的正确方式
enum {
   
    Success, NameErro, SexErro, StrErro, ScoreErro
};

typedef struct _Stu{
   
    char* name;
    char* sex;
    char* strNum;
    float* score;
}STU;

int InitStu(STU** pp)
{
   
    *pp = (STU*)malloc(sizeof(STU));

    (*pp)->name = (char*)malloc(40);
    if ((*pp)->name == NULL)
    {
   
        return NameErro;
    }

    (*pp)->sex = (char*)malloc(1);
    if ((*pp)->sex == NULL)
    {
   
        return SexErro;
    }

    (*pp)->strNum = (char*)malloc(40);
    if ((*pp)->strNum == NULL)
    {
   
        return StrErro;
    }

    (*pp)->score = (float*)malloc(4);
    if ((*pp)->score == NULL)
    {
   
        return ScoreErro;
    }
    return Success;
}

int main(int argc, char* argv[])
{
   
    STU* p = NULL;
    int ret = InitStu(&p);
    if (ret == Success)
    {
   
        p->name = (char*)"Damon";
        p->sex = (char*)"M";
        p->strNum = (char*)"146483983";
        *(p->score) = 100.0f;
    } else
    {
   
        return -1;
    }
    cout << "Success\n" << endl;
    cout << p->name << endl;
    cout << p->sex << endl;
    cout << p->strNum << endl;
    cout << *(p->score) << endl;


    system("pause");
    return 0;
}

4.3.4 二级指针的步长

所有类型的二级指针,由于均指向一级指针类型,一级指针类型大小是4,所以二级指针的步长也是4,这个信息很重要。

int main(int argc, char* argv[])
{
   
    char* p = 0;
    char** pp = &p;

    printf("p = %p p+1 = %p\n", p, p + 1);
    printf("pp = %p pp+1 = %p\n", pp, pp + 1);
    

    system("pause");
    return 0;
}

输出如下:
在这里插入图片描述

4.4 指针数组(字符指针数组)

4.4.1 定义

指针数组的本质就是数组,数组中的每一个i成员都是一个指针。形式如下:
在这里插入图片描述
语法解析: pArray先与“[]”结合,构成一个数组的定义,char*修饰的是数组的内容,即数组 的每个元素。

4.4.2 二级指针访问指针数组

4.4.2.1 指针数组名赋给二级指针的合理性

二级指针与指针数组名等价的原因:
char * * p 是二级指针;
char* array[N]; array=&array[0]; array[0] 本身是char*型;
char**p=array;

int main(int argc, char* argv[])
{
   
    const char* pa[] = {
    "Tencet", "Wangyi","Ali","ByteDance" };
    const char** pp = pa;
    for (int i = 0; i < sizeof(pa) / sizeof(*pa); i++)
    {
   
        cout << *(pp++) << endl;
    }

    system("pause");
    return 0;
}

但是我们还可以进行更进一步的优化。我们可以不依赖数组的长度来遍历了,我们可以判断,当指针指向NULL,遍历就结束了:

int main(int argc, char* argv[])
{
   
    const char* pa[] = {
    "Tencet", "Wangyi","Ali","ByteDance" ,NULL};
    const char** pp = pa;

    while ((*pp))
    {
   
        cout << *pp++ << endl;
    }

    system("pause");
    return 0;
}

也可以写成下面这样:

int main(int argc, char* argv[])
{
   
    const char* pa[] 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值