一.指针简介:
小端存储与大端存储
int a = 0x12345678
小端存储:
大端存储和小端存储位置相反
数组的存储是正向的,和其他类型不同,
如char A[3] = {2,3,4}
int *p;
int data = 10;
int arr[2] = {1,2};p = &arr[0];//数组的首地址就是首个元素的地址
p = a;//数组名就是数组的首地址#include <iostream>using namespace std;int main()
{int arr[] = {1, 6, 9};int *p;int *q;int num = 2222;p = #q = arr;cout << *p << endl;cout << *(q + 1) << endl;return 0;
}
二.指针的增量和数组的关系
#include <iostream>using namespace std;int main()
{int arr[] = {1, 2, 3};char arr1[] = {1, 2, 3};int *p;char *q;q = arr1;p = arr;cout << "p : " << *p << endl;cout << "address of p: " << p;cout << "q : " << *q << endl;cout << "address of q : " << q;// 指针的偏移量// 偏移量根基指针的类型// 偏移之后,还是他还是一个地址cout << *(p + 1) << endl;cout << "address of p+1: " << p << endl;cout << *(q + 1) << endl;cout << "address of q + 1: " << q + 1 << endl;return 0;
}
访问数组元素:
①指针法
1.偏移
2.取内容
*p++的优先级:
p先与++结合,后与*结合
②数组下标法
注意遍历数组之后,指针重新回到数组的首元素
但是这种写法是不被允许的
int arr[3] = {2,3,4};
int *p;
//p = arr;//由于p是int *类型,而i是int型,所以会报错
for(int i = 0,p = arr;i < 3; i++){cout << *p++ << endl;
}
三.对于两种方法的效率对比:
对于指针和数组下标的选择:
系统在使用数组下标对数组成员进行访问时,开销比较大,指针的访问效率远远大于数组名的访问效率
但是只有在指针访问正确时,才比数组下标访问更有效率(指针容易出错)
下标法更容易理解,在可读性方面也更有优势
见怪不怪的指针:
指针可以当作数组名进行下标法访问
数组名也可以当作指针进行偏移方式进行访问
但是数组名++ 是不可行的,因为指针p是一个指针变量,指针所指向的地址是可以改变的,但是arr是一个常量指针,指针常量不允许++
关于sizeof的区别
int arr[] = {1, 2, 3};int *p = arr;cout << "arr is " << sizeof(arr) << endl;cout << "p is " << sizeof(p) << endl;cout << "int is " << sizeof(int) << endl;cout << "int* is " << sizeof(int*) << endl;cout << "char is " << sizeof(char) << endl;cout << "char* is " << sizeof(char*) << endl;cout << "double is " << sizeof(double) << endl;cout << "float is " << sizeof(float) << endl;
sizeof(arr)指的是arr数组当中有三个元素 3*4=12
sizeof(p) 指的是计算机操作系统用4个字节表示一个地址
下列例子表示,此计算机的os指针都是用4个字节表示
四.关于指针常量和常量指针
常量指针和指针常量是指针的两种不同类型
它们有以下区别:
1.常量指针(const pointer):指针本身是常量,指针指向的值可以改变。声明时在*
前加const
关键字。
int x = 5;
int y = 10;
const int *ptr = &x;
ptr = &y; // 合法,可以改变指针指向的地址
*ptr = 15; // 非法,无法改变指针指向的值
2.指针常量(pointer to const):指针指向的值是常量,指针本身可以改变指向其他地址。声明时在*
后加const
关键字。
int x = 5;
int y = 10;
int *const ptr = &x;
*ptr = 15; // 合法,可以改变指针指向的值
ptr = &y; // 非法,无法改变指针指向的地址
一些关于这两个词的问题的答案帮助理解:
当涉及到指针常量和常量指针的概念时,可以设计一些问题来帮助加深理解。以下是一些关于指针常量和常量指针的问题:
1. 声明一个指针常量和一个常量指针,并解释它们之间的区别。
2. 给定一个指向整数的指针常量,如何修改指针指向的整数值?是否可以修改指针指向的地址?
3. 如果有一个常量指针指向一个整数,如何确保不能修改指针指向的值?
4. 在函数参数中如何声明一个指针常量和一个常量指针?这会对函数内部的操作有何影响?
5. 编写一个函数,接受一个常量指针作为参数,并尝试修改指针指向的值。观察会发生什么情况。这些问题可以帮助理解指针常量和常量指针的概念,以及它们在实际编程中的应用和限制。
以下是关于指针常量和常量指针问题的答案:
1. **指针常量和常量指针的区别**:
- 指针常量(pointer to const):指针本身是常量,指针指向的值不能被修改,但可以指向其他地址。
- 常量指针(const pointer):指针指向的值是常量,不能被修改,但指针本身可以指向其他地址。2. **修改指针常量指向的值**:
- 可以通过指针常量间接修改指向的值,如 `*ptr = newValue;`。
- 不能修改指针常量指向的地址,因为指针常量的地址是固定的。3. **确保常量指针指向的值不可修改**:
- 将指向的值声明为常量,如 `const int *ptr`。
- 尝试修改 `*ptr` 的值会导致编译错误。4. **在函数参数中声明指针常量和常量指针**:
- 声明指针常量:`void func(const int *ptr)`,防止在函数内修改指向的值。
- 声明常量指针:`void func(int *const ptr)`,防止在函数内修改指针指向的地址。5. **尝试修改常量指针指向的值**:
- 如果尝试在函数中修改常量指针指向的值,会导致编译错误,因为常量指针指向的值是只读的。
注意:
关于程序运行过程中出现的段错误,程序卡在半中间,不继续执行了
可以在编译时,加一个 -g选项
如gcc demo1.c -g 之后 gdb a.exe回车
加-g选项,会让编译出来的程序变成可调试
gdb就是用来调试程序的(使用gdb来找段错误)
输入指令之后,程序是待运行状态,输入 r 回车
程序进入调试
这样一步一步执行,如果遇到错误,程序就会终止,而且会提示出哪里出了问题
要退出输入q
y(yes)确定退出,n(no)取消退出
注:
关于vscode中出现中文输出乱码的问题:
vscode中文乱码问题及几种常见的解决方案-CSDN博客
关于scanf函数和cin的一点注意事项:
关于下列函数中
void initNum(int *parr, int size)
{for (int i = 0; i < size; i++){cout << "请输入第 " << i + 1 << "个数为:" << endl;scanf("%d", parr);parr++;}
}
scanf函数可以,而cin函数不行的原因是
在输入数组元素时使用了不同的输入方式,一个使用 `scanf` 函数,另一个使用 `cin` 对象。问题出在第二个函数中的 `cin >> parr;` 这行代码中。
原因是 `cin >> parr;` 中 `cin` 是输入流对象,而 `parr` 是一个指针,这里的操作会尝试将输入值存储到指针 `parr` 指向的地址,而不是数组元素本身。这种方式是错误的,因为应该将输入值存储到 `parr` 指向的地址,而不是修改指针本身。
正确的方式是使用 `*parr` 来访问指针指向的地址,并将输入值存储在该地址上,如 `cin >> *parr;`。这样可以确保输入的值被正确存储在数组中的每个元素位置上。
而在第一个函数中,`scanf` 是可以正常工作的,因为 `scanf` 函数是通过指针来接收输入值并将其存储在指针指向的地址上。这种方式与 `cin` 不同,`scanf` 需要传递指向变量地址的指针作为参数来接收输入值。
因此,对于 `scanf` 函数,可以通过 `scanf("%d", parr);` 来接收输入的整数值并将其存储在 `parr` 指向的地址上,这样可以正确地初始化数组中的各个元素。
利用指针代替数组,进行存储和遍历的完整的函数实现:
#include <iostream>using namespace std;void initNum(int *parr, int size)
{for (int i = 0; i < size; i++){cout << "请输入第 " << i + 1 << "个数为:" << endl;cin >> *parr++;}
}void printNum(int *parr, int size)
{cout << "您要输出的值是:" << endl;for (int i = 0; i < size; i++){cout << *parr++ << ' ';}
}int main()
{int arr[5];int *p = arr;int size = sizeof(arr) / sizeof(arr[0]);cout << size << endl;initNum(arr, size);printNum(&arr[0], size);return 0;
}
输入一个数组,将数组倒叙输出(利用指针)
void initArr(int *parr, int size)
{for (int i = 0; i < size; i++){cout << "请输入第" << i << "个数" << endl;cin >> *parr++;}
}
void reverseArr(int *parr, int size)
{for (int i = 0, j = size - 1 - i; i < size / 2; i++, j--){int temp = *(parr + i);*(parr + i) = *(parr + j);*(parr + j)= temp;}
}
void OutPut(int *parr, int size)
{for (int i = 0; i < size; i++){cout << *parr++ << ' ';}
}main函数:int arr[5];int *p = arr;int size = sizeof(arr) / sizeof(arr[0]);initArr(p, size);reverseArr(p, size);OutPut(p, size);return 0;
五.二维数组的地址认知、
int arr[3][3] = {{1, 2, 3}, {11, 22, 33}, {111, 222, 333}};
首先要明白,数组名是数组的首地址,arr是数组arr[0]的首地址,arr[0]
a[0] a[1] a[2]既然是数组名,而C语言中又规定了数组名是数组的首地址,
因此a[0]代表一位数组当中第0列元素的地址 a[0]是一个数组名,数组当中的
第零个元素(a[0][0])取一个地址 → &a[0][0]他是首地址,数组中数组名就是首地址
所以&a[0][0] 等价于 a[0] 首个元素的地址是数组的首地址
&a[0][0] == a[0] &a[0] = arr
也就是说a[1]的值是&a[1][0],a[2]的值是&a[2][0]
数组中首个元素取个地址,就是数组的首地址,也是数组名代表的地址
*a和a[0]是等价的
arr是一个二维数组,定义一个指针,取出来的首地址是一个数组,但是在C语言中没有对数组操作的概念,所以*a和a[0]是一个意思,也就是一层级之后,继续向里深入,a[0]代表的是二维数组的首地址
能够表示数组的首地址,两种方式
法一:数组名
法二:首个元素的地址
在数组当中 数组名就是数组的首地址 数组的首个元素取个地址,就是数组的首地址,也是数组名代表的数组的首地址
首个元素的地址是数组的首地址,数组名也是数组的首地址
六.关于malloc函数
int *p = (int *)malloc(sizeof(int));*p = 10;
这段代码的含义是:
1. `int *p = (int *)malloc(sizeof(int));`:这行代码首先声明了一个指针 `p`,类型为 `int *`,然后使用 `malloc` 函数动态分配了存储一个 `int` 类型数据大小的内存空间,并将这块内存的地址赋给指针 `p`。这意味着在堆内存中分配了足够存储一个整数的空间,并且指针 `p` 指向这块内存。
2. `*p = 10;`:这行代码将值 `10` 赋给指针 `p` 指向的内存地址,也就是将整数值 `10` 存储到了动态分配的内存空间中,相当于给这块内存赋值为 `10`。
总的来说,这段代码的作用是动态分配了一个整数大小的内存空间,并将整数值 `10` 存储在这块内存中,通过指针 `p` 来访问和操作这个动态分配的内存空间。在使用完这块内存后,应当使用 `free(p);` 来释放这块内存,以避免内存泄漏。
free(q)代表的是:把q指向的内存地址释放
malloc函数的用法及指针举例
#include <stdio.h>
#include <malloc.h>
#include <iostream>using namespace std;void f(int* p){*p = 200;
}int main()
{int* p = (int *)malloc(sizeof(int));*p = 10;cout << *p <<endl;f(p);cout << *p;return 0;}
结果:
动态内存分配与传统数组:
动态内存和静态内存的比较:
七.多级指针
了解多函数指针,是为了跨函数内存
int main()
{int i = 2;int *p = &i;int **q = &p;cout << i << endl;cout << p << endl;cout << *p << endl;cout << q << endl;cout << *q << endl;cout << **q << endl;return 0;
}
结果:
八.静态变量不能跨函数使用详解
上述这个程序中,虽然最终结果没有报错,但是第16行的语句有逻辑上的问题
在main函数中,调用了f函数,f函数之星完毕之后,就会释放掉在f函数中包括 i *q 的内存
所以*p 虽然等于&i ,但是此时 i 的内存已经被释放,p已经没有权限去读取i的值,而且i的值也不存在。
p中可以存 i 的地址,但是不能通过*p来读取 i 的值(不能访问 i 的空间,因为 i 的空间已经释放)
p是属于 main函数中的,可以对p进行各种形式的读写