目录
[1. 指针的基本概念](#1. 指针的基本概念)
[1.1 指针的定义](#1.1 指针的定义)
[1.2 取地址运算符(&)](#1.2 取地址运算符(&))
[1.3 间接引用运算符(*)](#1.3 间接引用运算符(*))
[2. 指针的基本操作](#2. 指针的基本操作)
[2.1 指针的赋值](#2.1 指针的赋值)
[2.2 空指针](#2.2 空指针)
[3. 指针和数组](#3. 指针和数组)
[3.1 数组和指针的关系](#3.1 数组和指针的关系)
[3.2 指针和数组的结合](#3.2 指针和数组的结合)
[4. 指针和函数](#4. 指针和函数)
[4.1 指针作为函数参数](#4.1 指针作为函数参数)
[5. 动态内存分配](#5. 动态内存分配)
[5.1 malloc 和 free 函数](#5.1 malloc 和 free 函数)
一、实验要求
- 掌握指针和间接访问的概念,会定义和使用指针变量。
- 能正确使用数组的指针和指向数组的指针变量。
- 能正确使用字符串的指针和指向字符串的指针变量。
二、实验原理
指针是C语言中非常重要且强大的概念之一。它提供了直接访问内存地址的能力,使得程序可以更加灵活地处理数据。
1. 指针的基本概念
1.1 指针的定义
指针是一个变量,其值是另一个变量的地址。通过指针,可以直接访问存储在该地址上的数据。
cpp
int *ptr; // 定义一个指向整数的指针
1.2 取地址运算符(&)
用于获取变量的地址。
cpp
int num = 10;
int *ptr = # // ptr指向num的地址
例如
cpp
#include<iostream>
using namespace std;
int main() {
int a=10,*ptr;
ptr = &a;
cout << a <<" "<<ptr;
return 0;
}
它的结果是
表面a的地址为000000ECA8AFF794,共16*4=64位二进制
那么下面的代码为什么地址不相邻呢?
cpp
#include<iostream>
using namespace std;
int main() {
int a=10,b=11,*ptr,*ptr1;
ptr = &a;
ptr1 = &b;
cout << a <<" "<<ptr<<endl;
cout << b << " " << ptr1;
return 0;
}
在C语言中,连续定义的两个变量的地址是否相邻,与多个因素有关,其中包括编译器、优化选项、操作系统的内存分配策略等。
对齐(Alignment): 许多体系结构要求数据按照某种规定的边界对齐,以提高访问速度。因此,编译器可能会在变量之间插入填充字节,以确保数据按照正确的边界对齐。这导致即使两个变量类型相同,它们的地址也可能不相邻。
优化: 编译器可能会对代码进行优化,包括对变量的存储和访问进行优化。这可能导致变量的地址不是按照它们在代码中的声明顺序来分配的。
内存分配策略: 操作系统对于内存的分配策略也可能影响变量的地址分布。例如,在某些情况下,操作系统可能会使用随机化技术来增加系统的安全性,这会导致变量的地址不再是连续的。
数据类型: 如果定义的变量类型不同,它们的大小可能也不同,这会影响它们在内存中的布局。
1.3 间接引用运算符(*)
用于访问指针所指向地址上的值。
cpp
int value = *ptr; // value等于num的值,通过ptr间接引用
例如
cpp
#include<iostream>
using namespace std;
int main() {
int a=10,*ptr,value;
ptr = &a;
value = *ptr;
cout << a <<" "<<ptr<<" " <<value<< endl;
return 0;
}
结果为
即指针ptr为地址,*ptr代表一个数,&a代表a的地址,&a和ptr可以交换值,属于同一类型
*ptr代表数,a代表数,两者可以交换值,属于同一类型
2. 指针的基本操作
2.1 指针的赋值
可以将一个指针指向另一个变量的地址。
cpp
int num1 = 10;
int num2 = 20;
int *ptr = &num1; // ptr指向num1的地址
ptr = &num2; // ptr现在指向num2的地址
例如
cpp
#include<iostream>
using namespace std;
int main() {
int num1 = 10, num2 = 20;
int* ptr = &num1;
cout << *ptr <<" "<<ptr<< endl;
ptr = &num2;
cout << *ptr << " " << ptr;
return 0;
}
结果为
2.2 空指针
指向地址为0的指针被称为空指针。
cpp
int *ptr = NULL; // ptr是一个空指针
可以猜测一下下述代码的输出结果是什么
cpp
#include<iostream>
using namespace std;
int main() {
int* ptr = NULL;
cout << *ptr <<" "<<ptr<< endl;
return 0;
}
答案是!
那空指针有什么作用呢?
1.标记未初始化的指针: 在定义指针变量但尚未为其分配有效内存地址之前,可以将指针初始化为
NULL
,表示它当前不指向任何有效的内存区域。2.避免野指针: 将指针初始化为
NULL
可以避免使用未初始化的指针(野指针),从而减少程序中出现的错误。3.指针作为空指针常量: 用
NULL
表示空指针常量,提高代码的可读性。在函数参数或返回值中,空指针常常用于表示某个指针不指向有效的内存。
cppvoid process_data(int *data) { if (data != NULL) { // 处理有效的数据 } else { // 处理空指针情况 } }
4.动态内存分配失败的标志: 在动态内存分配时,如果分配失败,
malloc
或calloc
通常会返回NULL
,这可用于检测内存分配是否成功。
cppint *dynamicPtr = (int*)malloc(sizeof(int)); if (dynamicPtr == NULL) { // 内存分配失败 // 处理错误的代码 }
5.函数返回空指针: 有时,函数可能返回一个空指针作为错误或特殊情况的标志。
cppint *find_element(int key) { // 查找元素... if (element_not_found) { return NULL; } // 找到元素,返回指向元素的指针 }
3. 指针和数组
3.1 数组和指针的关系
数组名本身就是一个指针,指向数组的第一个元素的地址。
cpp
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // ptr指向arr的第一个元素
记住是arr为地址,不是&arr!
3.2 指针和数组的结合
通过指针可以遍历数组的元素。
cpp
for (int i = 0; i < 5; ++i) {
printf("%d ", *(ptr + i)); // 打印数组元素的值
}
例如
cpp
#include<iostream>
using namespace std;
int main() {
int* ptr;
int a[5] = { 1,2,3,4,5 };
ptr = a;
for (int i = 0; i < 5; i++) {
cout << "第" << i << "个元素为" << *(a + i) << "其地址为"<<a+i<<endl;
}
return 0;
}
结果为
4. 指针和函数
4.1 指针作为函数参数
可以通过指针在函数间传递数据。
cpp
void modifyValue(int *ptr) {
*ptr = 100;
}
int main() {
int num = 10;
modifyValue(&num);
// 现在num的值变成了100
return 0;
}
但是如果不用指针呢?
cpp
#include<iostream>
using namespace std;
void modifyValue(int ptr) {
ptr = 100;
}
int main() {
int num = 10;
modifyValue(num);
cout << num;
return 0;
}
结果是10,所以指针的作用就体现出来了
在这段代码中,问题出现在modifyValue
函数的参数类型和传递方式上。在C中,函数参数可以通过值传递或引用传递。在这里,modifyValue
函数使用的是值传递,这意味着函数接收到的是实参的一个副本而不是实参本身。
当你调用modifyValue(num)
时,将num
的值(10)传递给modifyValue
函数的形参ptr
。在函数内部,ptr
被修改为100,但这仅仅是对形参的修改,不会影响实参 num
的值。
所以,当你在main
函数中输出num
的值时,输出的是原始的值,即10,而不是在modifyValue
函数内修改后的值100。
4.2 指针作为函数返回值
函数可以返回指针,使得函数能够返回动态分配的内存。
cpp
int* createArray(int size) {
int *arr = (int*)malloc(size * sizeof(int));
// 初始化数组...
return arr;
}
例如
cpp
#include<iostream>
using namespace std;
int* createArray(int size) {
int* arr = (int*)malloc(size * sizeof(int));
return arr;
}
int main() {
int* ptr;
ptr = createArray(5);
cout << ptr<<" "<<* ptr;
return 0;
}
结果为
为什么*ptr即数组的第一个值那么奇怪呢?
是因为数组只是分配了空间,并没有赋值
5. 动态内存分配
5.1 malloc
和 free
函数
用于动态分配和释放内存。
cpp
int *ptr = (int*)malloc(sizeof(int)); // 分配一个整数大小的内存
// 使用ptr...
free(ptr); // 释放内存
sizeof
是一个在 C 和 C++ 等编程语言中常用的操作符,用于获取数据类型或变量在内存中所占用的字节数。sizeof
的语法如下
cpp
sizeof(type)
sizeof(expression)
其中,type
是数据类型,而 expression
则是一个表达式或变量。sizeof
返回一个 size_t
类型的值,表示参数所占用的字节数。常用于分配内存。
malloc
是 C 语言中的一个函数,用于动态分配内存。它的名字来源于 "memory allocation"(内存分配)。malloc
函数接受一个参数 size
,表示要分配的内存字节数。它返回一个 void
指针,指向分配的内存的起始地址。可以将其转换为适当的类型,以便进行正确的使用。
上述转化为int型指针
free
函数用于释放通过动态内存分配函数(如 malloc
、calloc
、realloc
等)分配的内存空间。free
函数接受一个指针 ptr
,该指针应该是通过动态内存分配函数分配的内存的起始地址。调用 free
函数会将相应的内存空间标记为可用,以便后续的内存分配操作可以使用该空间。
三、实验内容
3.1
将n个数按输入时的顺序逆序排列,用函数实现。
代码
cpp
#include<iostream>
using namespace std;
void sort(int* p, int* q, int n) {
for (int i = 0; i < n; i++) {
*(q + i) = *(p + n - 1 - i);//将q指针所代表的数组的第n-i个元素赋值给p指针所代表的数组的第i+1个元素
}
}
int main() {
int a[100],b[100],n,*ptr1,*ptr2;
ptr1 = a;
ptr2 = b;
cout << "请输入所需要输入的元素数目:";
cin >> n;
for (int i = 0; i < n; i++) {
cin >> *(ptr1 + i);
}
sort(ptr1, ptr2, n);
for (int i = 0; i < n; i++) {
cout << *(ptr2 + i)<<" ";
}
return 0;
}
截图
分析
void sort(int* p, int* q, int n)
:这是一个排序函数,接受两个指向整数数组的指针p
和q
,以及数组的大小n
。该函数通过将数组p
中的元素逆序复制到数组q
中来实现排序。
int main()
:主函数包含以下步骤:
- 定义两个数组
a
和b
,以及两个指向整数的指针ptr1
和ptr2
。- 用户输入所需输入的元素数目
n
。- 通过指针
ptr1
输入数组a
的元素。- 调用
sort
函数,将数组a
中的元素逆序复制到数组b
中。- 输出数组
b
中的元素,即排序后的结果。在
sort
函数中,通过循环遍历数组,将数组p
中的元素逆序复制到数组q
中。这通过使用指针算术实现,*(q + i) = *(p + n - 1 - i)
将数组p
中的第n-i
个元素赋值给数组q
中的第i+1
个元素。
3.2
将一个5×5的矩阵(二维数组)中最大的元素放在中心,4个角分别放4个最小的元素(顺序为从左到右,从上到下依次从小到大存放),写一函数实现。用main函数调用。
代码
cpp
#include<iostream>
using namespace std;
int min_max = -9999, index = 0, b[4] = { 0,4,20,24 };//b数组为快速索引,index为min数组中最大元素的序列号
int find_min_max(int* ptr) {//找出四个元素中的最大值
min_max = -9999;
for (int i = 0; i < 4; i++) {
if (*(ptr + i) >= min_max) {
min_max = *(ptr + i);
index = i;
}
}
return min_max;
}
int compare(const void* a, const void* b) {
// 比较函数,用于指定排序规则
// 返回负数表示 a < b
// 返回零表示 a == b
// 返回正数表示 a > b
return (*(int*)a - *(int*)b);
}
void change(int *ptr) {
//找到最大元素和最小的四个元素
int max = -9999;
int min[4] = { 9999,9999,9999,9999 };
int* ptr2=min;
//找最大元素和最小的四个元素
for (int i = 0; i < 25; i++) {
if (*(ptr + i) > max) {
max = *(ptr + i);
}
min_max = find_min_max(ptr2);
if (*(ptr + i) < min_max) {//满足进入四个最小元素的数组
*(ptr2 + index) = *(ptr + i);//替换位置
}
}
//对四个最小元素的位置进行排序
qsort(ptr2, 4, sizeof(int), compare);
//更换位置
for (int i = 0; i < 25; i++) {
if (*(ptr + i) == max) {//如果是最大元素
int temp = *(ptr + 12);
*(ptr + 12) = max;
*(ptr + i) = temp;
continue;
}
}
for (int i = 0; i < 25; i++) {
for (int j = 0; j < 4; j++) {//如果是较小元素
if (*(ptr + i) == min[j]) {
int temp = *(ptr + b[j]);
cout << "temp " << temp << endl;
*(ptr + b[j]) = min[j];
*(ptr + i) = temp;
continue;
}
}
}
}
int main() {
int a[5][5],* ptr1;
ptr1 = a[0];
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
cin >> a[i][j];
}
}
change(ptr1);
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
cout<< a[i][j]<<" ";
}
cout << endl;
}
return 0;
}
截图
分析
变量定义和初始化:
int min_max = -9999, index = 0, b[4] = { 0,4,20,24 };
:定义了全局变量,其中min_max
用于存储四个元素中的最大值,index
用于存储min
数组中最大元素的序列号,b
数组是用于快速索引的数组。
find_min_max
函数:
- 该函数用于找出四个元素中的最大值,并返回这个最大值。它还会更新
index
变量,以表示最大值在min
数组中的位置。
compare
函数:
- 这是一个比较函数,用于在后续的
qsort
函数中进行数组排序。
change
函数:
- 该函数对数组进行操作,找到数组中的最大元素和最小的四个元素,然后将它们的位置进行调整。
- 具体操作:
- 找到数组中的最大元素
max
。- 找到数组中最小的四个元素,用
min
数组保存,并通过调用find_min_max
函数找到最小元素中的最大值及其位置。- 使用
qsort
对min
数组进行排序。- 将最大元素和最小元素的位置进行交换。
- 输出结果。
main
函数:
- 定义了一个5x5的数组
a
和一个指向该数组的指针ptr1
。- 通过用户输入给数组
a
赋值。- 调用
change
函数对数组进行操作。- 输出最终的数组。
指针是C语言中非常强大和灵活的特性,但也需要小心使用,因为错误的指针操作可能导致程序崩溃或产生不可预测的结果。
指针需慎用!