指针的基础
数据在内存当中是怎么样被存储的
数据在内存中的存储方式取决于数据的类型和计算机的体系结构
基本数据类型
整数类型:整数在内存中以二进制补码的形式存储。对于有符号整数,最高位为符号位,0 表示正数,1 表示负数。例如,在一个 32 位的系统中,int类型的整数5的二进制表示为00000000 00000000 00000000 00000101,而-5的二进制补码表示为11111111 11111111 11111111 11111011。不同大小的整数类型(如char、short、int、long等)所占用的内存字节数不同,其取值范围也不同。
浮点类型:浮点数在内存中的存储遵循 IEEE 754 标准。以单精度浮点数(float)为例,它占用 32 位内存,其中包括 1 位符号位、8 位指数位和 23 位尾数位。浮点数通过科学计数法的形式表示,指数部分采用偏移码存储,尾数部分则是二进制小数的有效数字。例如,浮点数3.14在内存中的存储形式是经过一系列转换后得到的二进制编码。
字符类型:字符类型通常占用 1 个字节的内存空间,其存储的是字符对应的 ASCII 码值或 Unicode 码值。例如,字符’A’的 ASCII 码值为 65,在内存中存储的二进制形式为01000001。
数组
数组是相同类型数据的集合,其元素在内存中是连续存储的。例如,一个包含 5 个int类型元素的数组int arr[5],在内存中会依次存储这 5 个整数,每个整数占用相应的字节数(如在 32 位系统中为 4 字节)。数组名可以看作是指向数组首元素的指针,通过指针的偏移可以访问数组中的各个元素。
结构体
结构体是不同类型数据的组合,其成员在内存中的存储顺序与结构体定义中的顺序一致。结构体的大小通常是其成员大小的总和,但由于内存对齐的要求,可能会存在一些额外的填充字节,以保证结构体的每个成员都能在适当的内存地址上对齐,从而提高访问效率。例如,定义一个结构体struct Student { char name[20]; int age; float score; };,其成员name、age和score会依次存储在内存中,并且可能会根据编译器的对齐规则进行适当的填充。
指针
指针存储的是内存地址,其大小通常与计算机的字长相同。例如,在 32 位系统中,指针占用 4 字节,而在 64 位系统中,指针占用 8 字节。指针所指向的数据类型决定了通过指针访问内存时所读取或写入的数据大小和格式。
字符串
在 C 语言中,字符串是以字符数组的形式存储的,并且以’\0’作为字符串的结束标志。例如,字符串"Hello"在内存中实际上存储为’H’、‘e’、‘l’、‘l’、‘o’、'\0’这 6 个字符的 ASCII 码值,占用 6 个字节的内存空间。而在一些高级语言中,字符串可能会有更复杂的存储结构和管理方式,但本质上也是存储字符序列的一种数据类型。
访问方式
直接访问
定义:直接访问是指通过变量名或地址直接对数据进行操作,即直接使用变量名来访问和修改其对应内存地址中的数据值。
示例:在大多数编程语言中,定义一个变量后,可以直接使用该变量名来进行赋值、读取等操作。例如在 C 语言中,int num = 10; 定义了一个整型变量 num 并初始化为 10,之后可以直接通过 num 来访问和修改这个值,如 num = 20; 就直接将变量 num 的值修改为 20。
优点:简单直观,易于理解和使用,代码的可读性较高,能够直接反映出对哪个变量进行了何种操作。
缺点:对于一些复杂的数据结构或需要动态分配内存的情况,直接访问可能不够灵活,难以对数据进行有效的组织和管理。
间接访问
定义:间接访问是指通过指针或引用等方式来访问数据,即先获取数据的地址,然后通过该地址来访问和操作数据。
示例:在 C 语言中,可以定义一个指针变量来存储另一个变量的地址,然后通过指针来间接访问该变量的值。例如,int num = 10; int *ptr = # 这里定义了一个指针变量 ptr,并将它指向变量 num 的地址,通过 *ptr 就可以间接访问和修改 num 的值,如 *ptr = 30; 此时 num 的值就被修改为 30 了。
优点:更加灵活,可以动态地分配和管理内存,方便地操作复杂的数据结构,如链表、树等。通过指针或引用,可以在不同的函数或模块之间共享和传递数据,而无需将数据本身进行复制,提高了程序的效率和内存的利用率。
缺点:使用间接访问需要对指针和内存管理有更深入的理解,否则容易出现指针错误,如空指针引用、野指针等,导致程序出现错误甚至崩溃。
#include <iostream>
using namespace std;int main() {int i = 0;cout << "请输入i的值" << endl;cin >> i;cout << "i = " << i << endl;//直接访问cout << "i在内存当中的地址是" << &i << endl;int* p = &i;cout << *p << endl;//间接访问cout << p << endl;cout << &p << endl;return 0;
}
指针变量作为函数参数传输
#include <iostream>
using namespace std;int swapNumbers(int * numPtr1, int* numPtr2)
{int tmp;tmp = *numPtr1;*numPtr1 = *numPtr2;*numPtr2 = tmp;return 0;
}int main() {int num1 = 90;int num2 = 88;swapNumbers(&num1,&num2);cout << "num1 = " << num1 << endl;cout << "num2 = " << num2 << endl;return 0;
}
指向数组的指针变量
#include <iostream>
using namespace std;int main() {int arr[] = {1,2,3,4};int* ptr = arr;int* ptr1 = &arr[0];for (int i = 0; i < sizeof(arr) / sizeof(int); i++){cout << *(ptr + i) << " ";}cout << endl;for (int i = 0; i < sizeof(arr) / sizeof(int); i++){cout << *(ptr1 + i) << " ";}return 0;
}
数组的第一个元素和数组名称指向的是同一个地址空间
引用
#include <iostream>
using namespace std;int add(int& a, int& b)
{cout << a + b << endl;return 0;
}int main() {int a = 99; int b = 1;add(a, b);return 0;
}
const关键字
常量变量定义
#include <iostream>int main() {// 定义一个常量整数,使用const关键字const int MAX_VALUE = 100;// 试图修改常量的值,会导致编译错误// MAX_VALUE = 200;std::cout << "常量MAX_VALUE的值为: " << MAX_VALUE << std::endl;return 0;
}
常量指针
#include <iostream>int main() {int num = 50;// 定义一个常量指针,指针指向的内容不能通过该指针修改const int* ptr = #// 下面这行代码会导致编译错误,因为不能通过常量指针修改所指向的值// *ptr = 100;std::cout << "通过常量指针获取的值为: " << *ptr << std::endl;return 0;
}
指针常量
#include <iostream>int main() {int num = 50;int num2 = 99;// 定义一个指针常量,指针本身的地址不能改变,但可以通过它修改所指向的值int* const ptr = #// 下面这行代码会导致编译错误,因为指针本身的地址不能改变//ptr = num2;*ptr = 100;std::cout << "修改后的值为: " << num << std::endl;return 0;
}![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/04721be84a0043bdb55e54457ad22754.png)
## 常引用```c
#include <iostream>int main() {int num = 50;// 定义一个常引用,引用所指向的内容不能通过该引用修改const int& ref = num;// 下面这行代码会导致编译错误,因为不能通过常引用修改所指向的值// ref = 100;std::cout << "通过常引用获取的值为: " << ref << std::endl;return 0;
}
函数参数中的 const
#include <iostream>// 函数声明,参数为常引用,保证函数内部不会修改传入的值
void printValue(const int& value) {std::cout << "传入的值为: " << value << std::endl;
}int main() {int num = 50;// 调用函数,传递变量的常引用printValue(num);return 0;
}