【C++内存管理】C++指针快速入门

1. 第一个指针程序

1.1 指针的概念

指针是 C++ 中的一种变量,其存储的是内存的地址。它指向的内存位置、大小和类型取决于指针的声明。指针可以申请新的内存,也可以指向已有的内存。

1.2 申请和指向内存

你可以使用 new 运算符为指针申请新的内存,或者让它指向一个已经存在的变量。使用 & 运算符可以获取变量的地址,此处的 & 读作 “取地址”。

int *p1 = new int; // 申请新的内存,并将其地址赋给 p1
*p1 = 101; // 将 101 存储到 p1 所指向的内存中

int i = 102;
int* p2 = &i; // 让 p2 指向变量 i 的内存
*p2 = 103; // 通过 p2 修改 i 的值

1.3 释放内存

你可以使用 delete 运算符释放指针所申请的内存。在释放之后,指针变量依然存在,但是其指向的内存已经被回收,不再可用。

delete p1; // 释放 p1 所指向的内存

1.4 指针的大小

无论指针的类型如何,它本身的大小是固定的。这是因为指针变量存储的是内存地址,而不是实际的数据。在 32 位系统中,指针的大小通常是 4 字节,在 64 位系统中,指针的大小通常是 8 字节。

cout << "sizeof(p1): " << sizeof(p1) << endl; // 输出指针 p1 的大小
cout << "sizeof(*p1): " << sizeof(*p1) << endl; // 输出 p1 所指向的数据的大小

2. 程序的内存地址划分

C++ 程序的内存通常被划分为以下几个区域:

  • 保留区:这个区域是为了防止程序访问空指针导致的错误而设立的,通常位于 0 地址。
  • 代码区:也被称为 .text 区,存储程序的机器指令。
  • 全局/静态存储区:包含了 .data(已初始化的全局变量和静态变量)和 .bss(未初始化的全局变量和静态变量)。
  • 堆区:程序在运行时可以从堆区动态分配内存和释放内存。
  • 栈区:用于存储函数的局部变量、函数参数以及返回地址等。
  • 内核区:存储系统级别的指令和数据。

3. 进程的内存与指针代码关系

指针代码直接操作内存,对应进程的内存布局。对全局、局部变量的访问,实质上是对静态存储区和栈区的访问。动态内存分配的new、delete操作,实质上是对堆区的操作。这就构成了进程内存与指针代码的直接关系。

4. 堆栈空间的初始化

4.1 栈空间

栈空间是程序自动分配和释放的,用于存放函数的局部变量、参数等。对于栈空间的数组,我们可以使用 memset 函数将其全部初始化为 0。

int arr1[10]; 
memset(arr1, 0, sizeof(arr1)); // 使用 sizeof 获取数组的大小

你也可以在定义数组时直接初始化数组的元素:

int arr2[5] = { 1, 2, 3, 4, 5 }; // 初始化一个大小为 5 的数组

4.2 堆空间

堆空间是由程序员负责管理的,我们可以使用 newdelete 在堆上分配和释放内存。堆空间的数组同样可以使用 memset 进行初始化,需要注意的是,对于非字符类型的数组,我们需要将 memset 的第三个参数乘以元素的大小。

int *parr1 = new int[1024]; // 在堆上分配一块能存放1024个int的内存
memset(parr1, 0, 1024 * sizeof(int)); // 将内存全部初始化为 0

在完成对堆空间的操作后,记得使用 delete[] 释放内存,并将指针设置为 nullptr,以防止野指针的产生。

delete[] parr1;
parr1 = nullptr;

5. C++11中的for遍历

C++11 引入了新的 for 循环语法,使得遍历数组和容器变得更加简单。以下是使用新的 for 循环语法遍历数组的例子:

int arr[] = { 1, 2, 3, 4, 5 };
for (int x : arr) {
    cout << x << " ";
}

在这个例子中,x 会依次取 arr 中的每一个元素的值,并在循环体中执行操作。这种 for 循环的形式也被称为范围 for 循环,它可以使你的代码更简洁,更易于阅读。

6. 二维数组的内存分配

6.1 栈上的二维数组

在栈上分配的二维数组需要在编译时确定其大小,我们不能在运行时动态改变其大小。在这个程序中,我们在栈上创建了多个二维数组,并用不同的方式进行初始化:

unsigned char arr1[2][ARRSIZE] = { {1,2,3},{4,5,6} };
unsigned char arr2[][ARRSIZE] = { 
    {1,2,3},
    {2,3,4},
    {3,4,5} };

这两个数组都是在栈上分配的,但是arr2的行数是由编译器根据初始化列表推断出来的。

同样地,我们还创建了两个三维数组:

int arr3[2][3][4] =
{
    {{1,2,3,4},{2,3,4,5},{1,2,3,4}},
    {{1,2,3,4},{2,3,4,5},{1,2,3,4}},
};
int arr4[][3][4] =
{
    {{1,2,3,4},{2,3,4,5},{1,2,3,4}},
    {{1,2,3,4},{2,3,4,5},{1,2,3,4}},
    {{1,2,3,4},{2,3,4,5},{1,2,3,4}},
};

6.2 堆上的二维数组

相比于栈,堆提供了更大的灵活性,我们可以在运行时动态分配任意大小的内存。在这个程序中,我们在堆上创建了多个一维数组:

int * parr1 = new int[1024];
auto parr2 = new unsigned char[psize];
auto parr3 = new int[psize];

6.3. 二维数组的遍历

在C++中,我们可以使用嵌套的for循环来遍历二维数组:

for (auto arr : arr2)
{
    for (int i = 0; i < ARRSIZE; i++)
    {
        cout << static_cast<int>(arr[i]) << " " << flush;
    }
    cout << endl;
}

此外,我们还可以使用基于范围的for循环来遍历数组:

for (auto s : str1) //只能访问数组
{
    cout << s << "-"<<flush;
}

6.8 二维数组的清理

对于在栈上分配的数组,它们会在离开作用域时自动被销毁,不需要我们手动清理。而对于在堆上分配的数组,我们需要使用delete[]

操作符手动进行清理,并且建议在清理后将指针设置为nullptr以避免野指针:

delete[] parr1;
parr1 = nullptr;
delete[] parr2;
parr2 = nullptr;

7. Void指针和指针类型转换

7.1 Void指针

void指针,又称为通用指针,可以指向任何类型的数据。以下代码展示了如何使用void指针:

int num = 1;
void* ptr = &num;

在上面的代码中,我们创建了一个void指针,并使其指向一个整数。

7.2 static_cast转换

static_cast是C++中最通用的转换方式,可以用来进行各种有关联性的类型转换。比如,我们可以使用static_cast将void指针转换为int指针:

int* ptr2 = static_cast<int*>(ptr); //void *

然而,static_cast有一些限制,它不能去掉const限定符,也不能转换无关联的类型。

7.3 C语言风格的转换

除了static_cast之外,我们还可以使用C语言风格的转换方式:

int* ptr3 = (int*)ptr;

这种转换方式没有类型检查,更容易出错,所以在C++中不推荐使用。

7.4 const_cast转换

const_cast主要用于去掉变量的const限定:

const int* cptr1 = new int[1024];
int* ptr6 = const_cast<int*>(cptr1); // 去掉 const

在上面的代码中,我们使用const_cast将一个const int指针转换为int指针。

7.5 reinterpret_cast转换

reinterpret_cast用于进行一些高级的、危险的转换,它可以将任何类型的指针转换为任何其他类型的指针:

unsigned char* ucptr = new unsigned char[1024];
auto ptr7 = reinterpret_cast<int*>(ucptr); // 替换指针类型

在上面的代码中,我们使用reinterpret_cast将一个unsigned char指针转换为int指针。

注意,尽管这些转换看起来很强大,但是如果使用不当,它们都有可能导致错误,所以在使用时要特别小心。

8. 常量指针和指针常量

8.1 常量指针

常量指针是指向常量的指针。它可以指向不同的地址,但是不能通过指针来修改其所指向的值。以下代码展示了如何使用常量指针:

const int i1 = 100;
const int* pi1 = &i1; // 指向常量的指针
//(*pi1)++; 指向的值不可修改

在上面的代码中,我们创建了一个指向常量的指针,然后试图通过指针修改其所指向的值,这是不被允许的。

8.2 指针常量

指针常量是指针本身是常量的指针。它可以修改其所指向的值,但是不能指向不同的地址。以下代码展示了如何使用指针常量:

int* const pi3 = new int;
//pi3++; 指针本身是常量 指向不能修改
*pi3 = 200; // 指向的值可以修改
delete pi3;

在上面的代码中,我们创建了一个指针常量,然后试图使其指向另一个地址,这是不被允许的。

8.3 常量指针和指针常量的区别

常量指针和指针常量是两种不同的概念。前者的指针可以改变,指向不同的地址,但是不能通过指针来修改其所指向的值;而后者的指针不可以改变,不能指向不同的地址,但是可以修改其所指向的值。

8.4 const修饰的指针常量

当const同时修饰指针和指针所指向的值时,该指针既不能改变指向,也不能修改其所指向的值:

const int* const pi4 = &i1; // 指向和值都不能修改

在上面的代码中,我们创建了一个指针,既不能改变指向,也不能修改其所指向的值。

9. 数组操作图像示例

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main()
{
	// 打印消息到控制台
	std::cout << "cppds.com" << endl;

	// 读取图像为灰度图像,图像路径为"lena_hed.jpg"
	auto img = imread("lena_hed.jpg", IMREAD_GRAYSCALE);

	// 获取图像的高度和宽度
	int height = img.rows;
	int width = img.cols;
	
	// 打印图像元素大小
	cout << "img.elemSize() = " << img.elemSize() << endl;
	
	// 显示原始图像
	imshow("test1", img);

	// 遍历图像的每一个像素
	for (int i = 0; i < height; i++)
	{
		for (int j = 0; j < width; j++)
		{
			// 获取当前像素值
			auto c = img.data[i * width + j];
			
			// 将当前像素值进行反色操作,黑色(0)变为白色(255),白色(255)变为黑色(0)
			img.data[i * width + j] = 255 - c;
		}
	}
	
	// 显示反色后的图像
	imshow("test2", img);
	
	// 暂停等待5000ms(5秒),如果在这个期间有键盘输入则立即返回,如果没有则等待指定时间后返回
	waitKey(5000);
	
	return 0;
}
  • imread函数用于读取指定路径的图像,这里以灰度模式读取。
  • imshow函数用于显示图像,这里分别显示了原图和处理后的图像。
  • 通过img.data[i * width + j]访问到图像的每一个像素,然后进行反色处理。
  • waitKey函数则是使得程序暂停,等待用户输入,这里的5000表示等待5000ms,即5秒。如果在这个时间内,用户按下任何键,waitKey函数就会立即返回,如果超过这个时间,函数也会返回。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 快速入门C是一本针对初学者的C语言学习指南,它以简洁明了的方式介绍了C语言的基本语法和常用编程技巧。这本书以PDF格式发布,可以方便地下载和阅读。 在《快速入门C》中,读者将学习如何设置和配置编程环境,包括安装C编译器和编辑器。通过简单易懂的示例代码,读者将逐步了解C语言中的变量、运算符、控制流语句等基本概念。此外,该书还介绍了C语言中的函数、数组、指针等重要概念,并通过实践演示如何使用它们解决常见的编程问题。 《快速入门C》还提供了一些高级的主题,如结构体和文件操作。这些主题将帮助读者更深入地了解C语言的特性和功能。 这本书的优点在于其简明扼要的风格和易于理解的讲解。它适合那些希望快速入门C语言的初学者,尤其是那些有编程基础的读者。此外,它还附带了许多练习题和实践项目,帮助读者巩固所学知识。 总之,《快速入门C》是一本适合初学者的C语言学习指南,它以PDF格式发布,方便读者下载和阅读。通过学习这本书,读者将快速掌握C语言的基本语法和常用编程技巧,并能够应用它们解决实际问题。 ### 回答2: 快速入门C语言可以通过阅读相关的PDF文档进行学习。 C语言是一种通用的编程语言,广泛应用于计算机科学和软件开发领域。为了快速入门C语言,您可以寻找一些PDF文件,这些文件提供了关于C语言基础知识和常见的编程技巧的详细介绍。在这些文件中,您可以找到如下内容: 1. 语法和基本概念:C语言具有自己的语法和规则,您可以学习如何定义变量、控制流程和函数等基本概念。 2. 数据类型和运算符:了解C语言中的不同数据类型(如整数、字符、浮点数等)以及各种运算符(如算术、逻辑、比较等)的使用方法。 3. 数组和指针:数组和指针是C语言的重要特性,您可以学习如何声明和使用数组,并了解指针的概念和用法。 4. 流程控制:学习如何使用条件语句(如if-else语句和switch语句)和循环结构(如for循环和while循环)来控制程序的执行流程。 5. 函数和库函数:学习如何定义和调用函数,并了解一些常用的C标准库函数,如输入输出函数(如printf和scanf)、数学函数等。 6. 编译和调试:了解如何使用C语言编译器将源代码转换为可执行文件,并学习一些基本的调试技巧,以便检查和修复程序中的错误。 通过阅读这些PDF文档,您可以逐步了解C语言的基础知识,并通过编写简单的代码来巩固所学内容。为了更好地掌握C语言,建议您在阅读文档的同时进行实践,编写一些简单的程序并进行调试,以加深对C语言的理解和掌握。 ### 回答3: 快速入门C PDF是一本介绍C语言基础知识和入门技巧的电子书籍。这本书以简洁明了的方式,帮助读者快速理解和掌握C语言的基础概念和语法规则。 首先,本书从C语言的起源和历史背景开始介绍,帮助读者了解C语言的发展过程和重要性。然后,书中详细介绍了C语言的基本数据类型、变量、运算符等基础知识,让读者能够了解和使用C语言的基本语法和逻辑。 接下来,本书重点介绍了C语言的流程控制语句,包括条件语句、循环语句和跳转语句等。通过对这些语句的讲解和示例演示,使读者能够掌握如何使用这些语句来实现程序的流程控制和逻辑判断。 此外,本书还详细介绍了C语言的函数和数组,包括函数的定义、调用和传参等内容,以及数组的定义和使用方法。读者通过学习这些内容,能够了解C语言中函数和数组在程序设计中的重要性和作用。 最后,本书通过一些实例和练习题,帮助读者巩固和实践所学的知识。同时,本书还提供了一些常用的C语言编程技巧和注意事项,以及一些常见的C语言编程错误和解决方法。读者通过实践和思考,能够进一步提升自己的C语言编程水平。 总的来说,快速入门C PDF是一本适合初学者学习C语言的电子书籍。通过简明扼要的介绍和示例,使学习者能够快速入门C语言,并能够在实践中应用所学的知识。这本书对于想要学习C语言或者提升C语言编程水平的读者来说,是一本实用的参考书。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ricky_0528

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

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

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

打赏作者

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

抵扣说明:

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

余额充值