图文并茂的C++指针、数组与字符串学习笔记

发现更多的计算机知识,欢迎访问xiaocr的个人网站

6.1数组

数组是具有一定顺序关系的若干对象的集合体,组成数组的对象称为该数组的元素。

6.1.1数组的声明和使用
1.数组的声明

(1)确定数组的名称

(2)确定数组元素的类型

(3)确定数组的结构(数组维数,大小)

声明的一般形式
数据类型 标识符[常量表达式][常量表达式]


2.数组的使用

使用数组的时候只能够对数组的各个元素进行操作

数组的每个元素都是想相同的类型

注意(不能够一次引用整个数组):

(1)数组元素的下标表达式可以是任意合法的算术表达式.其结果必须为整数

(2)数组元素的下标值不得超过声明时所确定的上下界.否则运行时将出现数组越界错误


tips:数组越界编译器不一定会提示错误的!可能会导致不可预期的错误


6.1.2数组的存储和初始化
1.数组的储存

数组元素在内存当中是顺序、连续存储的,在内存当中占一组连续的存储单元


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Nq3oHTKv-1684500139356)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230505114628968.png?lastModify=1683434623)]


2.数组的初始化

数组的初始化就是在声明数组时给部分或全部元素赋值

细节:

细节当指定的初值个数小于数组大小时,剩下的数组元素会被以0赋值。若定义数组时没有指定任何一个元素的初值,对于静态生存期的数组,每个元素仍然会被以0赋值;但对于动态生存期的数组,每个元素的初值都是不确定的

特别的,对于多维数组,第一维是可以省略的

#include<iostream>  
using namespace std;  
int main() {  
    int a[2][3] = { 1,0,0,0,1,0 };  
    //省略第一维  
    int b[ ][3] = { 1,0,0,0,1,0 };  
      
    int c[2][3] = { {1,0,0},{0,1,0} };  
    return 0;  
}

6.1.3数组作为函数参数

数组元素和数组名都可以作为函数的参数以实现函数间的数据的传递和共享

如果使用数组名做函数的参数.则实参和形参都应该是数组名.且类型要相同。和普通 变量做参数不同,使用数组名传递数据时,传递的是地址。形参数组和实参数组的首地址重 合.后面的元素按照各自在内存中的存储顺序进行对应.对应元素使用相同的数据存储地 址,因此实参数组的元素个数不应该少于形参数组的元素个数。如果在被调函数中对形参 数组元素值进行改变.主调函数中实参数组的相应元素值也会改变,这是值得特别注意的


总结一维数组:

一维数组名称的用途

  1. 可以统计整个数组在内存中的长度

  2. 可以获取数组在内存中的首地址

示例:

int main() {  
​  
    //数组名用途  
    //1、可以获取整个数组占用内存空间大小  
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };  
​  
    cout << "整个数组所占内存空间为: " << sizeof(arr) << endl;  
    cout << "每个元素所占内存空间为: " << sizeof(arr[0]) << endl;  
    cout << "数组的元素个数为: " << sizeof(arr) / sizeof(arr[0]) << endl;  
​  
    //2、可以通过数组名获取到数组首地址  
    cout << "数组首地址为: " << (int)arr << endl;  
    cout << "数组中第一个元素地址为: " << (int)&arr[0] << endl;  
    cout << "数组中第二个元素地址为: " << (int)&arr[1] << endl;  
​  
    //arr = 100; 错误,数组名是常量,因此不可以赋值  
​  
​  
    system("pause");  
​  
    return 0;  
}

注意:数组名是常量,不可以赋值

总结1:直接打印数组名,可以查看数组所占内存的首地址

总结2:对数组名进行sizeof,可以获取整个数组占内存空间的大小


6.1.4对象数组

数组的元素不仅可以是基本数据类型.也可以是自定义类型。例如,要存储和处理某单位全体雇员的信息,就可以建立一个雇员类的对象数组。对象数组的元素是对象,不仅具有数据成员.而且还有函数成员。因此,和基本类型数组相比.对象数组有一些特殊之处

声明和调用:

声明一个一维对象数组的语句形式是
类名 数组名[常量表达式];
与基本类型数组一样,在使用对象数组时也只能引用单个数组元素。每个数组元素都
是一个对象,通过这个对象,便可以访问到它的公有成员,一般形式是;
数组名 [下标表达式].成员名


6.2指针

b是C++从C中继承过来的重要数据类型.它提供了一种较为直接的地址操作手段。正确地使用指针,可以方便、灵活而有效地组织和表示复杂的数据结构。动态内存分配和管理也离不开指针。同时.指针也是C++的主要难点


6.2.1内存空间的访问形式

计算机的内存储器被划分为一个个的存储单元。存储单元按一定的规则编号,这个编号就是存储单元的地址。地址编码的最基本单位是字节.每字节由8个二进制位组成.也就是说每字节是一个基本内存单元,有一个地址。计算机就是通过这种地址编号的方式来管理内存数据读写的准确定位的。 在C++程序中如何利用内存单元存取数据的呢?一是通过变量名.二是通过地址。

程序中声明的变量是要占据一定的内存空间的.

例如,对于一些常见的32位系统.short型占2字节.long型占4字节。具有静态生存期的变量在程序开始运行之前就已经被分配了内存空间。具有动态生存期的变量,是在程序运行时遇到变量声明语句时被分配内存空间的

在变量获得内存空间的同时,变量名也就成为相应内存空间的名称,在变量的整个生存期内都可以用这个名字访问该内存空间,表现在程序语句中就是通过变量名存取变量内容。但是.有时使用变量名不够方便或者根本没有变量名可用.这时就需要直接用地址来访问内有单元。例如.在不同的函数之间传送大量数据时.如果不传递变量的值.只传递变量的地址就会减小系统开销.提高效率。如果是动态分配的内存单元(将在6.3节介绍),则根本就没 有名称.这时只能通过地址访问。

内存结构简化示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vddAndBm-1684500139358)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230505121254398.png?lastModify=1683434623)]


6.2.2指针变量的声明

指针也是一种(复合型)数据类型,具有指针类型的变量称为指针变量。指针变量是用于存放内存单元地址的通过变量名访问一个变量是直接的.而通过指针访问一个变量是间接的。

就好像你要找一位学生,不知道他住哪,但是知道201房间里有他的地址.走进201房间后看到一张字条;“找我请到302”。这时你按照字条上的地址到302去,便顺利地找到了他。这个201房间,就相当于一个指针变量.字条上的字便是指针变量中存放的内容,而住在302房间的学生便是指针所指向的对象值指针也是先声明.后使用.

声明指针的语法形式是 *数据类型 标识符

int *ptr;

表示这里声明的是一个指针类型的变量。“数据类型"可以是任意类型,指的是指针所指向的对象(包括变量和类的对象)的类型,这说明了指针所指的内存单元可以用于存放什么类型的数据.称之为指针的类型。


6.2.3“&”与“*”

"*"称为指针运算符,也称解析(dereference),表示获取指针所指向的变量的值,这是一个一元操作符。

“&”称为取地址运算符,也是应该一元操作符,用来得到一个对象的地址

int *p//声明p是一个int型指针
cout<< *p//输出指针p所指向的内容
int &r //声明一个int型的引用r


6.2.4指针的赋值
  • 指针赋值的两种方法:

(1) 在定义指针的同时进行初始化赋值.语法形式为:

存储类型 数据类型 *指针名 = 初始地址;

(2) 在定义之后,单独使用赋值语句,赋值语句的语法形式为

指针名 = 地址;

这里注意一点,数组名其实就是一个不能被赋值的指针。即指针常量

int a[10]; //定义int型指针
int * ptr = a; //定义并初始化指针


  • 可以声明指针类型的常量.这时指针本身的值不能被改变。

int * const p = &a;
p = &bl //错误,指针常量不能进行赋值操作


  • void类型指针

(3)一般情况下,指针的值只能赋给相同类型的指针。但是有一种特殊的 void类型指针,可以存储任何类型的对象地址,就是说任何类型的指针都可以赋值给void类型的指针变量。经过使用类型显式转换.通过void类型的指针便可以访问任何类型的数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z2mgazS0-1684500139358)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230505170544995.png?lastModify=1683434623)]


6.2.5指针运算

指针是一种数据类型与其他数据类型一样,指针恋量也可以参与部分运算

看一个实例理解即可:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D3YDm8ZU-1684500139359)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230505170702877.png?lastModify=1683434623)]

简化的内存图理解:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UPrbCXfQ-1684500139359)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230505170931660.png?lastModify=1683434623)]


6.2.6指针处理数组元素

tips:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mus2Ag9c-1684500139359)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230505170750646.png?lastModify=1683434623)]


处理实例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ZgM84Lt-1684500139360)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230505170834659.png?lastModify=1683434623)]


6.2.7指针数组

指针数组本质上也是一个数组,但是呢数组里面的元素比较特殊,存储的元素是指针(pointer).

  • 声明

*数据类型 数组名[下标表达式];

  • 注意

由于指针数组的每个元素都是指针元素,赋初值是必不可少的

实例:

利用指针数组输出单位年阵单位矩阵是主对角线元素为1.其余元素为0的矩阵,本例是一个3行3列的单位矩阵

#include<iostream>  
using namespace std;  
int main() {  
    int line1[] = { 1,0,0 };    //定义数组,矩阵的第一行  
    int line2[] = { 0,1,0 };    //定义数组,矩阵的第二行  
    int line3[] = { 0,0,1 };    //定义数组,矩阵的第三行  
​  
    //定义整型指针数组并初始化  
    int* pLine[3] = { line1,line2,line3 };  
​  
    cout << "Matrix test :" << endl;    //输出单位矩阵  
    for (int i = 0; i < 3; i++)  
    {  
        for (int j = 0; j < 3; j++)  
            cout << pLine[j][i] << " ";  
        cout << endl;  
    }  
    return 0;  
}


运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7tV54Bbv-1684500139360)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230505171959339.png?lastModify=1683434623)]


6.2.8用指针作为函数参数

指针作为函数的形参的三个作用:

第一个作用是使实参与形参指针指向共同的内存空间,以达到参数传递的目的。

第二个作用是减少函数调用时的数据传递的开销

第三个作用就是通过指向函数的指针传递函数代码的首地址


6.2.9指针型函数

指针型函数就是返回值为指针的函数。

主要目的就是就是要在函数结束时把大量的数据从被调函数返回到主调函数中。

通常指针型函数调用结束后 ,只能返回一个变量或者对象

数据类型 *函数名(参数表)
{
函数体
}

函数返回数组指针

由于数组不能被复制,因此函数不能返回数组,但是可以返回数组的指针。

一个帮助理解的例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZmDvyeVq-1684500139360)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230512154231454.png?lastModify=1683877350)]


6.2.10指向函数的指针

之前讲的所有的指针都是指向数据的指针,函数指针就是专门用来存放函数独爱吗首地址的变量

一般语法:

数据类型 (*函数指针名)(形参表)

数据类型说明函数指针所指函数的返回值类型;第一个圆括号中的内容指明一个函数指针的名称;形参表则列出了该指针所指函数的形参类型和个数

提示:由于对函数指针的定义在那式上比较复杂,如果在程序中出现多个这样的定义,多次重复这样的定义会相当烦琐,一个很好的解决办法是使用typedef。例如:

typedef int(*DoubleIntFuction)(double);
//这个声明了有一个double形参、返回值类型为int的核函数的指针
DoubleIntFuction funcPtr;//直接声明使用

函数指针在使用之前也要进行赋值,使指针指向一个已经存在的函数的起始地址

函数指针名 = 函数名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WuqQHssL-1684500139361)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230512155136337.png?lastModify=1683877350)]


6.2.11对象指针
1.概念:

和基本类型的变量一样.每个对象在初始化之后都会在内存中占有一定的空间。因此.既可以通过对象名.也可以通过对象地址来访问一个对象。虽然对象是同时包含了数据和函数两种成员.与一般变量略有不同.但是对象所占据的内存空间只是用于存放数据成员的**.函数成员不在每一个对象中存储副本**。对象指针就是用于存放对象地址的变量

对象指针遵循一般变量指针的各种规则.声明对象指针的一般语法形式为

Point *pointPtr //声明Point类的对象指针变量pointPtr
Point p1; //声明Point类的对象p1
pointPtr = &p1; //pointPtr 指向p1

补充一点:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bTT7YdGT-1684500139361)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230512155635288.png?lastModify=1683877350)]


2.this指针

this指针是一个隐含于每一个类的非静态成员函数中的特殊指针,它用于指向正在被成员函数操作的对象

细节:

this指针实际上是类成员函数的一个隐含参数。在调用类的成员函数时,目的对象的地址会自动作为该参数的值.传递给被调用的成员函数.这样被调函数就能够通过this指针来访同目的对象的数据成员。对于常成员函数来说.这个隐含的参数是常指针类型的

this就是*const

注意:

this只能在成员函数中使用,始终指向对象。

静态函数属于类而不属于类的实例,this指针是对实例进行操作的。静态函数是在类函数内部无this指针


3.指向类的非静态成员的指针

类成员自身也是一些变量函数或者对象等.因此也可以直接将它们的地址存放到个指针变量中样.就可以使指针直接指向对象的成员.进而可以通过这些指针访问对象的成员

一般形式:

类型说明符 类名::*指针名 //声明指向数据成员的指针
类型说明符 (类名::*指针名)(参数表)//声明指向函数成员的指针

由于类是通过对象实例化的,访问数据成员的时候可以结合下面两种语法形式

对象名.*类成员指针名
对象指针名->*类成员指针名

注意:

成员函数指针在声明之后要用以下形式的语句对其进行赋值

指针名 = &类名::函数成员名

调用成员函数也有两种办法

  • (对象名.* 类成员指针名)(参数表)

  • (对象指针名->*类成员指针名)(参数表)

4.指向类的静态成员的指针

对类的静态成员的访问是不依赖于对象的.因此可以用普通的指针来指向和访问静态成员


6.3动态分配内存

在C++中,动态内存分配是一种在程序运行时动态地分配内存的方式。这种内存分配方式允许程序在运行时根据需要动态地分配内存,从而提高程序的灵活性和效率。

动态内存分配主要使用两个运算符:new和delete。new运算符用于动态地分配内存,而delete运算符用于释放已经分配的内存

动态内存分配的一般步骤如下:

  1. 使用new运算符动态分配所需要的内存空间。

  2. 对所分配的内存空间进行初始化。

  3. 使用分配的内存空间进行必要的操作。

  4. 使用delete运算符释放分配的内存空间。

例如,如果要动态地分配一个整数变量,可以使用下面的代码:

int* p = new int;
*p = 10;

在这个例子中,new运算符动态地分配了一个整数变量的内存空间,并将返回的指针存储在指针变量p中。然后,我们可以使用指针p对分配的内存空间进行操作。最后,使用delete运算符释放分配的内存空间:

delete p;

需要注意的是,在使用动态内存分配时,我们需要确保在不需要使用分配的内存空间时及时释放它,否则可能会导致内存泄漏(未及时释放是的内存越来越大)。

tips:

对于基本数据类型.如果不希望在分配内存后设定初值,可以把括号省去.例如: int * point=new int;

如果保留括号,但,则表示用0对该对象初始化,例如: int *point=new int(); 如果建立的对象是某一个类的实例对象,就是要根据初始化参数列表的参数类型和个数调用该类的构造函数.

new建立一个类的对象时候,要调用构造函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UReyIAGp-1684500139362)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230512162802972.png?lastModify=1683877350)]


1.动态数组:

在C++中,可以使用new运算符动态地创建数组类型的对象,这种数组称为动态数组。与静态数组不同,动态数组的大小可以在程序运行时动态地确定,从而提高程序的灵活性和效率。

动态数组的创建需要指定数组元素的个数,语法如下:

type* arrayName = new type [arraySize];

其中,type表示数组元素的类型,arrayName表示数组的名称,arraySize表示数组元素的个数。

例如,下面的代码创建了一个包含10个整数的动态数组:

int* arr = new int[10];

在创建动态数组后,可以使用指针变量arr对数组进行操作,如下所示:

for (int i = 0; i < 10; ++i) {
arr[i] = i;
}

需要注意的是,在使用动态数组时,需要记得使用delete运算符释放分配的内存空间,避免内存泄漏。释放动态数组的语法如下:

delete[] arr;

其中,delete[]运算符用于释放动态数组的内存空间。

需要注意的是,动态数组的大小一旦确定,就无法再更改大小。如果需要更改数组大小,可以使用更高级的数据结构,如vector或list。同时,使用动态数组时需要注意内存管理,避免内存泄漏和越界访问等问题。

2.高维动态数组

两种理解方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VnUCBSm7-1684500139362)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230512163320797.png?lastModify=1683877350)]


在C++中,可以使用new运算符创建更高维度的数组,例如二维数组、三维数组等。创建更高维度的数组需要在new运算符中指定每个维度的大小。

以二维数组为例,创建一个包含3行4列的二维数组可以使用如下代码:

int** arr = new int*[3]; // 创建3行
for (int i = 0; i < 3; ++i) {
arr[i] = new int[4]; // 每行创建4个元素
}

这段代码首先创建了一个包含3个元素的指针数组arr,每个元素都是一个指向int类型的指针。然后,使用for循环对每个指针元素分别分配一个包含4个int类型元素的一维数组。

二维数组arr可以像普通二维数组一样进行访问,例如:

arr[0][0] = 1;
arr[1][2] = 2;

使用二维指针创建的二维数组需要注意内存的释放,需要先释放每一行的元素数组,再释放指针数组本身:

for (int i = 0; i < 3; ++i) {
delete[] arr[i];
}
delete[] arr;

类似地,创建三维数组需要在new运算符中指定每个维度的大小,例如:

int*** arr = new int**[3]; // 创建3个二维数组
for (int i = 0; i < 3; ++i) {
arr[i] = new int*[4]; // 每个二维数组有4行
for (int j = 0; j < 4; ++j) {
arr[i][j] = new int[5]; // 每行有5个元素
}
}

创建更高维度的数组同样需要注意内存的释放和越界访问等问题。建议在使用动态多维数组时,使用高级数据结构如vector或者使用封装好的数组类库,这样能够减少错误的发生。


6.4字符串

字符串实质上是隐含创建的char类型数组,并在末尾添加"\0"作为结尾的标记。

一个字符串常量表示的就是这个数组的首地址。由于常量值是不能改的,可以将他赋值给其他的指针。

string类

string类提供了对字符串进行处理所需要的的操作。

1.构造函数的原型

string(); //默认构造函数
string(const string &rhs); //复制构造函数
string (const char *s)//用指针s指向的字符串常量初始化

提示:

由于string类具有接受const char *类型的构造函数,因此字符串常量和用字符数组表示的字符串变量都可以隐含地转换为string对象

string str = “Hello World!”

2.string类的操作符

string 类提供了丰富的操作符:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NYz6dHUu-1684500139362)(file:///C:/Users/Lenovo/AppData/Roaming/Typora/typora-user-images/image-20230519154710902.png?lastModify=1684481436)]


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Cr不是铬

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

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

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

打赏作者

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

抵扣说明:

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

余额充值