当前位置:网站首页>【C语言】详解指针的初阶和进阶以及注意点(1)
【C语言】详解指针的初阶和进阶以及注意点(1)
2022-07-02 11:50:00 【Ahao_te】
一、必备的指针基础
什么是指针?
指针理解的两点:
- 指针是内存中的一个最小单元编号,也就是地址
- 口头说的指针,通常是指针变量,这个变量存放内存地址
所以指针是地址,口头说的指针是指指针变量。
如:
*说明p是一个指针,指针变量p存放a的地址,所以p的值是地址,类型是int*
int a=10;
int *p=&a;
这是最开始,之后会有其它很复杂的指针,理解p是什么很重要。
指针的大小多大?怎么来的?
所以,对于在32位平台下
#include<stdio.h>
int main()
{
printf("%d\n", sizeof(int*)); //4
printf("%d\n", sizeof(char*)); //4
printf("%d\n", sizeof(float*)); //4
return 0;
}
指针类型的意义是什么?
下面来看这样一个例子
一个指针指向一个char类型变量的地址,指针+1指向下一个char类型变量的地址,char类型大小一个字节对应地址+1,而int类型就是4个字节,所以地址+4。(注意这里+1和地址本身大小4/8字节无关,别搞混了)
#include<stdio.h>
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("%p\n", &n); //006FFBB8
printf("%p\n", pc); //006FFBB8
printf("%p\n", pi); //006FFBB8
printf("%p\n", pc + 1); //006FFBB9
printf("%p\n", pi + 1); //006FFBBC
return 0;
}
可知不同类型的指针,在指针移动的距离上,取决于指针指向的地址类型。
指针的解引用
*表示解引用操作符
第一种情况 这种情况" * "的意思是表示pa是一个指针
int a=10;
char* pa=&a;
第二种情况 这种情况" * "表示对指针pa进行解引用,将pa指针变量储存的地址解引用,表示对应地址的变量本身。
int a=10;
char* pa=&a;
*pa=20;
看下面这个例子
对char * 类型的指针解引用只访问1个字节,对int * 类型的指针解引用访问4个字节。
int main()
{
int n = 0x11223344;
printf("%x\n", n);//11223344
char* pc = (char*)&n;
*pc = 0;
printf("%x\n", n);//11223300
int* pi = &n;
*pi = 0;
printf("%x\n", n);//0
return 0;
}
指针的类型也决定了,对指针解引用的时候能操作几个字节
为什么是 *pc=0后是11223300,而不是00223344,可以看看小端与大端存储
野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的)
#include<stdio.h>
int main()
{
int* p; //局部变量未初始化,值为0xcccccccc,随机值
*p = 20; //未初始化,不能解引用,程序报错
return 0;
}
二、指针进阶
1、字符指针
先来以下代码
int main()
{
//指针指向单个字符,pc存ch地址
char ch = 'w';
char* pc = &ch;
*pc = 'b';
printf("%c\n", ch);//b
//把首字符a的地址赋值给了p,常量字符串不可改变所以加个const
const char* p = "abcdef";
char arr[] = "abcdef";
//*p = 'w';//err
*arr = 'w';//首元素a->w
//遇到‘\0’终止打印
printf("%s\n", p);//abcdef
printf("%s\n", arr);//wbcdef
return 0;
}
值得注意的是,对于字符串,指针存的只有首字符的地址。
一道经典题:
int main()
{
const char* p1 = "abcdef";
const char* p2 = "abcdef";
char arr1[] = "abcdef";
char arr2[] = "abcdef";
//p1==p2 因为p1和p2都存的是字符a的地址,
//而常量字符串放在只读内存中(只读不写),就没必要存在多份一样的
if (p1 == p2)
printf("p1==p2\n");
else
printf("p1!=p2\n");
//需要创建两个不同的数组 首元素的地址不一样
if (arr1 == arr2)
printf("arr1==arr2\n");
else
printf("arr1!=arr2\n");
return 0;
}
2、指针数组和数组指针
指针数组:数组里面存的是指针
通常是以下形式
//[]优先级大于*,所以先是arr1[5]数组,数组里面数据类型是int*
//arr1类型是int* [5]
int* arr1[5];
//arr2[5]数组,数组里面数据类型是int**
int** arr2[5];
用指针数组存入其它数组的首地址,打印二维数组
int main()
{
int arr1[] = {
1,2,3,4,5 };
int arr2[] = {
2,3,4,5,6 };
int arr3[] = {
3,4,5,6,7 };
int* parr[3] = {
arr1,arr2,arr3 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", *(*(parr + i) + j));
}
printf("\n");
}
}
数组指针:指针指向数组
通常是以下形式
//指针parr1指向一个数组,数组里面有5个数据,数据类型为int。
//parr1类型是int (*)[5]
int (*parr1)[5];
//指针parr2指向一个数组,数组里面有5个数据,数据类型为int*
//parr2类型是int* (*)[5]
int* (*parr2)[5];
数组指针一般存储的地址是整个数组地址
注意:数组名的两个特例
int main()
{
int arr[10] = {
0 };
int (*p)[10] = &arr;
//数组名代表首元素地址
printf("%p\n", arr); //006FF834
printf("%p\n", arr + 1); //006FF838
//首元素地址
printf("%p\n", &arr[0]); //006FF834
printf("%p\n", &arr[0] + 1); //006FF838
//数组地址代表的是整个数组的地址
printf("%p\n", &arr); //006FF834
printf("%p\n", &arr + 1); //006FF85C
//sizeof(arr) 只有数组名的时候代表整个数组大小,注意是只有
int sz = sizeof(arr);
//这时就代表了地址大小
int sz1 = sizeof(arr+0);
printf("%d\n", sz);//40
printf("%d\n", sz1);//4
return 0;
}
1.sizeof(数组名) 整个数组的大小
2.&数组名,这里的数组名表示的依然是整个数组,&数组名表示整个数组的地址
数组指针指向数组,那么数组指针应该储存的是数组地址:
//数组指针p指向二维数组首行数组地址
void print2(int(*p)[5],int r, int c)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
//p+i选定哪一行,*(p+i)可理解为解引用一维数组地址就成了一维数组名所代表的首元素地址
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {
1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
print2(arr, 3, 5); //二维数组名表示第一行数组的地址。
printf("%p\n", arr);//00EFFB18
printf("%p\n", arr + 1);//00EFFB2C 加20 arr代表第一行数组的地址,+1跨越一个一维数组
printf("%p\n", *arr + 1);//00EFFB1C 加4 *arr代表第一行数组第一个元素地址,+1跨越一个数
}
让我们来看看数组指针和指针数组放在一起。
//先看括号里,arr1[5]代表一个数组,这个数组类型是int (*)[10],这个数组的存储5个指针,
//每个指针指向一个含有10个int类型的数组。
int (*parr1[5])[10]; //存放数组指针的数组
3、函数参数和指针参数
1、一维数组传参
一维数组都有以下传参方式
void test(int arr[])
{
}
void test(int* arr)
{
}
void test(int arr[10])
{
}
void test2(int *arr[20])
{
}
//指针数组里存的是一级指针,数组名是首元素地址,二级指针存的是一级指针的地址
void test2(int **arr)
{
}
int main()
{
int arr[10] = {
0 };
int* arr2[20] = {
0 };
test(arr);
test2(arr2);
return 0;
}
2、二维数组传参
二维数组值得注意的是,一般数组名表达的是首行一维数组的地址
void test(int arr[3][5])//可以这样写
{
}
void test(int arr[][])//不能 第二个[]的数不能省去
{
}
void test(int arr[][5])//可以这样写
{
}
void test(int* arr) //不能 一维数组不能表示二维数组
{
}
void test(int* arr[5]) //不能 这是指针数组不能表示
{
}
void test(int (*arr)[5]) //可以 数组指针存放一位数组地址
{
}
void test(int **arr) //不能 二级指针存的是一级指针地址
{
}
int main()
{
int arr[3][5] = {
0 };
test(arr); //arr表示第一行一维数组的地址
return 0;
}
3、一级指针传参
一级指针很简单
void print(int *p)
{
}
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9};
int *p = arr;
print(p);
return 0;
}
4、二级指针传参
记住:二级指针存放的是一级指针的地址
void test(char **p)
{
}
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);//可以
test(ppc);//可以
test(arr);//可以 arr代表数组首元素地址,首元素是一级指针
return 0;
}
4、函数指针
先让我们来看看函数的地址是否可以用平常方式表示。
int test(int x,int y)
{
printf("\n");
return x+y;
}
int main()
{
printf("%p\n", test);//001313FC
printf("%p\n", &test); //001313FC
return 0;
}
有了地址,接下来该怎么保存?
首先需要一个储存地址的指针*(pfun)=&test,然后一定需要类型int,最后加上参数,就完成了地址的保存
int *(pfun)(int,int)=&test;
看以下代码
int Add(int x, int y)
{
return x + y;
}
int main()
{
//pf先和*结合,说明pf是指针,指针指向的是一个函数,指向的函数参数为(int, int),返回值类型为int。
int (*pf)(int, int) = Add; //保存函数地址
int ret = (*pf)(2, 3); //通过指针访问
printf("%d", ret);
return 0;
}
5、总结
本节重点在于,指针变量的理解、一维和二维数组的数组名的注意项、数组指针和指针数组、数组和指针的传参。
指针总体一直是C语言很难的问题,但将其拆分一小个小个的理解就会发现非常简单。
这次的第一节先将指针的基础和进阶中的基础总结完成,下一节进阶内容需要通过总和上面的内容理解,所以理解好这节内容非常重要。
边栏推荐
- Wechat applet uses towxml to display formula
- 求轮廓最大内接圆
- Slashgear shares 2021 life changing technology products, which are somewhat unexpected
- Fabric. JS manual bold text iText
- Full of knowledge points, how to use JMeter to generate encrypted data and write it to the database? Don't collect it quickly
- Fabric. JS zoom canvas
- Uniapp automated test learning
- C语言中的算术运算及相关练习题
- 871. 最低加油次数 : 简单优先队列(堆)贪心题
- YoloV6训练:训练自己数据集遇到的各种问题
猜你喜欢
Actual combat sharing of shutter screen acquisition
buuctf-pwn write-ups (7)
为什么只会编程的程序员无法成为优秀的开发者?
Simple verification code generator for 51 single chip microcomputer experiment
c语言入门--数组
YoloV6训练:训练自己数据集遇到的各种问题
LeetCode 2310. The number of digits is the sum of integers of K
Fabric. JS free draw circle
Add vector formula in rich text editor (MathType for TinyMCE, visual addition)
mathjax 入门(web显示数学公式,矢量的)
随机推荐
forEach的错误用法,你都学废了吗
STM32标准固件库函数名(一)
Edit the formula with MathType, and set it to include only mathjax syntax when copying and pasting
obsidian安装第三方插件——无法加载插件
ONNX+TensorRT:将预处理操作写入ONNX并完成TRT部署
Socket and socket address
mathML转latex
fatal: unsafe repository is owned by someone else 的解决方法
taobao. trade. Get (get some information of a single transaction), Taobao store order interface, Taobao oauth2.0 interface, Taobao R2 interface code docking and sharing
mathjax 入门(web显示数学公式,矢量的)
btrace-(字节码)动态跟踪工具
Fundamentals of software testing
Reuse and distribution
LeetCode 209. 长度最小的子数组
socket(套接字)与socket地址
电脑怎么设置扬声器播放麦克风的声音
Introduction to mathjax (web display of mathematical formulas, vector)
There is no solution to the decryption error of the remote user 'sa' and the service master password mapped from the remote server 'to the local user' (null) /sa '
用户隐私协议有些汉字编码不规范导致网页显示乱码,需要统一找出来处理一下
fatal: unsafe repository is owned by someone else 的解决方法