【C++笔记】四、复杂数据类型(下)

18.指针基础

18.1 变量中存储的是什么内容

变量存储在内存空间中。

int n = 100;
float value = 123.321;

cout << "n = " << n << endl;    // 100 
cout << "n address = " << &n << endl;   // 0x7fff5fbffb4c  每位十六进制数表示4位二进制的数
/* 实际上,对于64位系统来说,地址应是64位二进制,即8字节,地址完整版应该是 0x00007fff5fbffb4c, 只不过前面的0都省略了*/
// 即,16位十六进制 <==> 64位二进制,两位16进制数(对应8位二进制数)表示1字节
/* 注意:1位十六进制数和16位二进制是不一样的,1位十六进制数对应4位二进制数,是半字节,16位二进制对应2字节 */

// 对于32位系统来说,地址应是32位二进制,对应4字节

cout << "value = " << value << endl;   // 123.321
cout << "value address = " << &value << endl;   // 0x7fff5fbffb28  ,前面的0000省略了
 //报错,long类型和指针类型所占用的空间是一样的,但二者不能直接赋值
long pointer  = &value; 

long pointer  = long(&value); 
cout << "pointer = " << pointer << endl;//十进制数140734799805208,是16进制地址值的十进制版本

18.2 如何在程序中使用变量

// 指针的用途
int abc[10] = {1};   // 最多存10个数
abc[123] = 3;    // 存超了,但在C++中是允许的,但可能使程序崩溃

int abc[200] = {1};   // 这样可以防止程序崩溃,但浪费了内存,指针就可以解决这个问题
abc[123] = 3;    

int *pn = &n;    // 定义指针变量
float *pvalue = &value;  // 定义了指向float类型变量的指针

cout << "pn = " << pn << endl;   // 以十六进制方式输出地址
cout << "pvalue = " << pvalue << endl;
    
cout << "*pn = " << *pn << endl;    // 100
cout << "*pvalue = " << *pvalue << endl;     // 123.321

cout << sizeof(*pn) << endl;    //  4

//pointer本身是一个long类型的值,通过(float*)可以强制转换为指针类型
cout << "*pvalue = " << *((float*)pointer) << endl;   // 123.321

19.指针移动与数据类型

int numbers[2] {25,26};
// int *numbers; 默认指向数组的第一个数:numbers[0], numbers[1]

cout << "numbers[0]=" << *numbers << endl;  // 25 
cout << "numbers[1]=" << *(numbers+1) << endl;  // 26

char chars[2] {25, 26};     
cout << "chars[0]=" << int(*chars) << endl;     //  25
cout << "chars[1]=" << int(*(chars+1)) << endl;  //   26

void *p = numbers;
cout << "chars[1]=" << int(*(p+1)) << endl;  // 报错

25对应二进制:11001

26对应二进制:11010

int类型的变量在内存中占4字节,每字节8位,一共32位。char类型变量在内存中占1字节,

指向int类型的变量+1时,指针移动4字节的位置,指向char类型的变量+1时,指针移动1字节位置

void *p = numbers;
cout << "chars[1]=" << int(*(p+1)) << endl;  // 报错

因为指针指向了void类型,这是个不确定的类型,指针不知道该移动多少位,所以会报错
想移动指针的话,就需要指定指针所指向的数据类型

20.指针的初始化

int xyz = 30;
int *p;
cout << "p address=" << p << endl; // 打印0x0

int xyz = 30;
int *p = &xyz;
cout << "p address=" << p << endl;  // 0x7fff5fbffb4c

p = &xyz;    
*p = 123;
    
cout << "xyz = " << xyz << endl;   // 123
    
xyz = 456;
cout << "*p = " << *p << endl;    //  456

21.使用new关键字分配内存空间

int xyz = 100;  
int *p = &xyz;    // 初始化指针还需要单独定义一个变量,相对麻烦

// 在C语言中,可以使用malloc分配内存空间,C++同样可以使用,但new更好一些
// typename *pointer = new typename
int *pointer = new int;  // 分配了4字节大小的内存空间,并把内存空间的首地址赋给了pointer
*pointer = 123;
cout << "*pointer=" << *pointer << endl;   // 123

// 区分:
int xyz = 100;   // 分配了4字节的内存空间,且该内存空间被命名为xyz
int *pointer = new int;     /* 并没有给分配的内存空间命名,所以不能通过名字使用new分配的内存空间,使用该内存空间的唯一方法就是使用指向该内存空间首地址的指针pointer,pointer是内存空间首地址的名字,而xyz是整个内存空间的名字  */

double *value  = new double;
*value = 33.22;
cout << "*value=" << *value << endl;  // 33.22

// 若new分配内存空间失败,会抛出异常

22.使用delete释放内存空间

/*
  1.  delete只能释放使用new分配的内存空间,不能释放使用malloc以及类似的函数分配的内存空间
  2.  普通变量系统会自动回收占用的内存空间,但使用new、malloc等动态分配的内存空间(在堆上),系统不会自动回收
  3.  delete以后,指针本身和指针指向的内存空间的数据不会被清空,除非再次写入数据
  4.  有多个指针指向了相同的内存空间,只要释放一个,其他的也会释放
  5.  对于已经释放了的指针所指向的内存空间,不能再次释放。
*/

int *n = new int;
*n = 123;
cout << "n address before delete:" << n << endl;   // 0x100103a40

// 1 2
delete n;  // 不加*
int xyz = 300;

cout << "n address after delete:" << n << endl;  // 0x100103a40
cout << "n = " << *n << endl;   // 123

// 3
// 使用new动态分配内存空间时,会在内存空间上给一个标志,表示这块内存空间不要分配给任何其他的变量了,使用delete表示将该标志清除了,该内存空间随时都可能分配给其他变量
*n = 456;  /* 尽量不要这样用,这块内存空间的数据虽然还在,但空间已经被释放了,随时可能被其他变量占用 */ 
cout << "n = " << *n << endl;   // 456

int xyz = 300;
n = &xyz;  // 这样是没有任何问题的,因为xyz在栈上

// 4
int *m = new int;
*m = 444;
int *p = m;   // p和m指向了同一段具有4字节长度的内存空间
delete p;  // 相当于释放了m

// 5
delete p;  //  抛出异常  
delete  m;   //  抛出异常

23.动态创建和释放数组

int n = 20;
int array1[10];   // 数组长度在定义时需要指定
int array2[n];

int *array3 = new int[n];
int *array4;

array4 = new int[100];
//array4 = new int[200];  /* 此时会造成内存的泄露,因为new int[100];的内存空间没有被释放,就又定义了新的空间,这就会导致原来400字节的内存空间就不会分配给任何变量了,即使程序退出,内存空间也不会被释放  */

delete [] array4;
// array4 = new int[200]  // 这样就没问题

  // array4 = NULL;  // 0,将指针变量/数组设为空,在delete之后最好加上这个
array4 = nullptr;  // 在c++ 11中尽量使用nullptr代替NULL
int *p = new int;
delete p;
p = nullptr;   // delete后最好是将指针设置为空
array4 = new int[200];

24.使用动态创建的数组

int *array = new int[10]{1,2};  // 分配了能存储10个int类型值的空间,并初始化前2个数组元素
array[2] = 30; // 也可以单独为某一个数组元素赋值
array[3] = 50;

cout << "array[0]=" << array[0] << endl;  // 1
cout << "array[3]=" << array[3] << endl;  // 50

// 使用指针的方式引用数组元素
cout << "array[2]=" << *(array + 2) << endl;  // 30

int codeArray[3]{10,20, 30};
cout << "codeArray[0] = " << codeArray[0] << endl;   // 10
cout << "codeArray[1] = " << *(codeArray + 1) << endl;  // 20
cout << "codeArray[2] = " << *(codeArray + 2) << endl;   // 30
    
array++;
cout << "array[1]=" << *array << endl; // 2
array--; // 又指回了存储空间的首地址

codeArray++;  // 报错,这是不允许的,对于非指针数组,不能用自加或自减

// 释放数组的内存空间
delete [] array;
array = nullptr; 

25.指针与字符串

char str[10] = "hello";  // 定义了字符串
cout << str << endl;   // hello

int pp = 3;
char *p_str = "hello"; //不建议这样定义,相当于指向常量字符字符的字符串指针,不能修改每一个字符
cout << p_str << endl;  // hello

char *p = str;    // p指向str,str修改,p也会修改
cout << p << endl;   /* 打印hello,注意,这里输出的不是h的地址,而是整个hello,由于C++标准库中I/O类对<<操作符重载,因此在遇到字符型指针时会将其当作字符串名来处理,输出指针所指的字符串 */

p = new char[strlen(str) + 1];   // 给字符串结束符分配一个空间
strcpy(p, str);    /* 该函数会自动添加结束符,第一个参数是目标内存空间首地址,第二个参数是待复制内容的地址 */
    
str[1] = 'x';
cout << str << endl;    // hxllo

*(p + 2) = 'w';   // p是指向了新的内存空间,str修改以后,并不影响p
cout << p << endl;    //  hewlo

char *p_str = "hello"; //相当于指向常量字符字符的字符串指针,不能修改每一个字符,不建议这样定义
*p_str = 's';   // 报错

delete [] p;
delete [] p_str;  // 报错,delete[]只能释放new分配的内存空间

26.常量指针与指针常量

26.1 常量指针

//  常量指针
const int code = 1234;  //  常量在定义时必须初始化
// code = 3;  //  报错,不能更改,常量分配在栈上,只读,不允许更改
const int numbers[] = {1,2,3,4};
//  numbers[2] = 44;  // 报错

const int *p = new int(100); // 常量指针通常在声明时初始化
// *p = 333;  // 报错,常量指针所指向的是常量,不允许通过该指针更改其所指向的值
cout << *p << endl;
    
char str[10] = "hello";
const char *p_str = str;  // 指向字符串的常量指针不允许修改字符
// str可以更改,但*p_str不能更改
//*p_str = 'a';   // 报错,p_str是常量指针,不允许通过该指针更改其所指向的值

26.2 指针常量

char * const p_str1 = str;
*p_str1 = '1';    // 可以更改
p_str1 = new char[3];   // 报错,无法让指针指向新的内存空间

const char * const p_str1 = str;
*p_str1 = '1';    // 报错
p_str1 = new char[3];   // 报错,无法让指针指向新的内存空间

27.使用new动态创建结构体

#include <iostream>
using namespace std;
int main(int argc, const char * argv[])
{
//  用new创建结构体对象 ①② 

    struct MyStruct
    {
        int code;
        char *name;
        double price;
    };
    
    MyStruct *p = new MyStruct();   

    // 用结构体指针访问结构体成员时,使用' -> '    
    p->name = "abc";  /* 报错,name本身是字符指针,所以new MyStruct()只是给name分配了指针大小的内存,name所指向的内存还没有分配,所以需要给name所指向的地方分配内存 */ 
    p->name = new char[30];   // 一共能存储30个字符大小/类型的数据,②
    
    strcpy(p->name, "iPhone6 plus 256G");
    
    (*p).code = 1234;   // 使用结构体指针访问成员的另一种访问方式
    
    p->price = 9999;
    
    cout << "p->name = " << p->name << endl;   // iPhone6 plus 256G
    cout << "(*p).code = " << (*p).code << endl;   // 1234
    cout << "p->price = " << p->price << endl;     // 9999

    return 0;
}

28.使用new动态创建共用体

union MyUnion
{
     int code1;
     long code2;
};
    
MyUnion *p = new MyUnion();   //  使用new动态创建共用体
p->code1 = 200;
cout << "p->code1 = " << (*p).code1 << endl;  // 200
cout << "p->code2 = " << (*p).code2 << endl;  // 200
    
(*p).code2 = 400;
cout << "p->code1 = " << p->code1 << endl;   /* 400,只要设置的数小于int的范围,code1和code2就相等 */

29.多维数组和多级指针

29.1 多维数组

int codes1[10] = {1,2,3};  // 一维数组


int codes2[5][10];   // 二维数组

/*
用1维数组表示二维数组:求codes1[8]在codes2[5][10]中的索引:
取整计算行索引  取余计算列索引 : 8/5 = 1(第2行,索引从0开始)  8 % 5 = 3(第4列,索引从0开始)所以codes1[8]对应的二维数组索引是:codes1[1][3]
由二维数组推算一维数组索引: codes1[1][2]  1 * 5 + 2 = 7 = codes1[7]
*/
// 一维数组初始化方式 —— 初始化第二行的第一个元素为123
int codes2[5][10] = {1,2,3,4,5,6,7,8,9,0,123/*codes2[1][0]*/};  // 前十个都要‘陪跑’,很麻烦
cout << "codes2[1][0] = " << codes2[1][0] << endl;   //  123

// 二维数组初始化方式 —— 初始化第5行的第10个元素为200
int codes2[5][10] =  {{1,2},{4,5},{},{},{1,2,3,4,5,6,7,8,9,200}};
cout << "codes2[4][9] = " << codes2[4][9] << endl;  // 200

// 三维数组初始化 5通道10行100列,即5个10x100的二维数组
int codes3[5][10][100] = {{{1,2}/*初始化了codes3第一个通道第一行的前两列元素(一共100列),codes3[0][0][...]*/,{4,5}/*初始化了codes3第一个通道第二行的前两列元素(一共100列),codes3[0][1][...]*/},{}/*换通道了codes3[1][...]*/};
cout << "codes3[0][0][1] = " << codes3[0][0][1] << endl;    // 2  第1通道第1行第2列元素
cout << "codes3[0][1][0] = " << codes3[0][1][0] << endl;    // 4  第1通道第2行第1列元素

// 设置数组的值
codes1[0] = 123;
codes2[0][0] = 445;
codes3[0][0][2] = 234;

29.2 多级指针

int codes1[10] = {1,2,3};  // 一维数组
int codes2[5][10] =  {{1,2},{4,5},{},{},{1,2,3,4,5,6,7,8,9,200}};
int codes3[5][10][100] = {{{1,2},{4,5}},{}};

int *p1 = codes1; // 单指针  p1存的是地址,地址所指向的内存空间存的是int值
// int **pp2  = codes2;  // 报错
int **pp2  = (int**)codes2;//双指针,pp2存的是地址,该地址所指向的内存中存的是指针(也是地址)
// cout << pp2[2][3] << endl;  // 报错

int *pp1 = new int;   // 没问题
// int **pp2 = new int[10][10]  /* 报错,这句话只是给pp2赋值了,并没有给pp2里面的内容创建新的内存空间 */

int **ppp2 = (int**)new int[10][10];  
// **ppp2 = 3;   // 报错

int* xyz[10];     // 数组中的元素是指向int值的指针
// *xyz[0] = 3;   // 报错,给xyz数组的第一个指针所指向的内存空间写值

int* xyz[10]; 
xyz[0] = new int;  // 要给第一个指针所指向的地方分配内存空间
*xyz[0] = 3;       // 正确

int **pppp2 = new int*[10];    // 正常,为二级指针分配长度为10个int*的内存空间
for(int i =0; i < 10;i++)
{
    *(pppp2+i) = new int[10]; // 为每一个元素(int*)分配长度为10个int的内存空间
}
    
**pppp2 = 203;   // 正确

**(pppp2 + 1) = 100;   //二级指针加一,表示行加一,是往下挪了一行;用数组方式如何取得100?
cout << "**(pppp2 + 1) = " << pppp2[0][1] << endl;   // 0,没取到100
cout << "**(pppp2 + 1) = " << pppp2[1][0] << endl;   // 100

30.vector模板类基础

#include <iostream>
#include <vector>   // 声明库函数
using namespace std;

int main(int argc, const char * argv[])
{
    //  vector模板类可以看作动态数组
    //  可以作为数组或栈使用

    vector<int> values;   // vector中的元素只能存储int类型值,values为vector类型变量

    //  动态添加三个元素
    values.push_back(20);    // 第一个
    values.push_back(40);    // 第二个
    values.push_back(60);    // 第三个
    
    for(int n:values)    // 枚举value中的所有元素
    {
        cout << n << endl;     // 20 40 60
    }

    // 删除指定的元素
    // values.pop_back();  // 出栈,删除最后一个元素,60没了
    //values.erase(values.begin());  //  删除第1个元素,20没了
    values.erase(values.begin() + 1);  //  删除第2个元素,40没了

    // 获得vector尺寸
    long size = values.size();
    cout << "values size = " << size << endl;   // 2个元素,不是2字节
    for(long i = size - 1; i>=0;i--)   // 通过数组的方式倒序枚举vector变量中的值
    {
        cout << values[i] << endl;   // 60  20
    }
    cout << values.at(0) << endl;    // 20,利用.at索引数组中的元素
    
    return 0;
}

31.数组模板

数组和数组模板的区别

  1. 数组和数组模板在后期初始化上不同
  2. 数组的尺寸允许使用变量,但数组模板的尺寸不允许使用变量,但可以使用常量或数值
  3. 数组在某种程度上可以当做指针使用,但数组模板不可以
  4. 数组模板可以更容易获取数组的尺寸( 用.size() )
  5. 将数组赋给指针是引用复制,而数组模板之间的赋值是值的复制    

    注意:不管是数组,还是数组模板,都不要超出最大的索引

#include <iostream>
#include <array>   // 声明
using namespace std;
int main(int argc, const char * argv[]) {
    array<string, 5> productNames = {"iPhone6", "iPhone6 Plus"};

    array<int,3> codes;
    codes = {200,300,400};  // 数组模板可以先定义再整体初始化
    
    int codes1[3]; //普通数组如果不在定义时初始化,之后就只能对单个元素初始化,不能整体的初始化
    codes1[0] = 200;   // 没问题
    //  codes1 = {1,2,3};  //  语法错误
    
    int n = 100;     // 变量
    int codes1[n];   // 定义数组尺寸可以使用变量

    const int size = 3;   // 常量
    // int size = 3;     // 数组模板处会报错
    array<int,size> codes;
    codes = {200,300,400};  
    
    cout << "productNames[0] = " << productNames[0] << endl;

    // 数组模板的索引方法和数组一样
    if(productNames[4] == "")
    {
        cout << "productNames[4]未初始化" << endl;
    }
    
    cout << "codes[2] = " << codes[2] << endl;

    int abc[3] = {1,2,3};
    cout << "abc[0] = " << *abc << endl;  /* 1 ,数组和指针在某种程度上是通用的,对数组名进行取值操作,会取到首个元素的值  */
    
    cout << "productNames size is " << productNames.size() << endl;  /* 打印数组模板的尺寸(元素个数) */
    cout << "abc size is " << sizeof(abc)/sizeof(int) << endl;  /* 打印数组的尺寸(元素个数) */
    
    //abc[5] = 3;   // 不报错,C++编译器不检测边界
    // productNames[10] = 4;  // 不报错,但有可能输出是乱码
    // cout << "productNames[10] = " << productNames[10] << endl;  // 输出的是乱码

    //   枚举数组模板中的元素
    for(int i = 0;i < 2;i++)
    {
        cout << productNames[i] << endl;   // iPhone6   iPhone6 Plus
    }
    
    for(int value:codes)
    {
        cout << value << endl;    // 200 300 400
    }

    // 数组模板的赋值  
    array<int , 3> codes1;
    codes1 = codes;    // 这是值的赋值,不是引用的赋值,所以更改codes1不会影响codes
    codes1[0] = 123;
    
    cout << "codes1[0] = " << codes1[0] << endl;   //  123
    cout << "codes[0] = " << codes[0] << endl;     //  200
    
    int codes2[]{1,2,3};
    // int codes3[] = codes2;  // 在语法上不允许,数组间的直接赋值
    int *pCodes = codes2;    // 用指针可以,是引用的复制
    pCodes[0] = 321;    // codes2的值也会跟着变
    cout << "codes2[0] = " << codes2[0] << endl;   // 321
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DUANDAUNNN

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值