- 1.前言:
- 2.指针是什么?
- 2.1 指针定义
- 2.2拓展
- 3.指针和指针类型
- 3.1指针
- 3.2指针类型
- 3.2.1指针的+-操作
- 3.2.2指针的解引用
- 3.3总结
- 4.野指针
- 5指针的运算
- 5.1指针+-整数
- 5.2指针-指针
- 5.3指针的关系运算
- 6指针和数组
- 7二级指针
- 8.指针数组
- 9const 修饰指针
- 9.1const在*左边
- 9.2const 在*右边
- 9.3总结
- 10例题
- 10.1库函数strlen()的实现
- 10.2库函数strcpy()的实现
- 11Ending
在上一篇文章介绍了数组的基本内容,这篇博客则主要对指针的内容做一个初步的介绍与总结,给大家介绍指针的定义与应用、以及指针数组等,为之后指针进阶打下基础。
为了防止以后大家找不到了,所以家人们在浏览观看是记得点个赞加收藏
谭老爷子书中给出由于地址可以指向变量单元,所以我们将地址形象化为指针,意思是可以通过它找到以它为地址的内存单元。
定义指针的一般形式:
类型名 * 指针变量名 (类型名)也叫指针变量的基类型
有些拗口,我们看一段代码:
#define _CRT_SECURE_NO_WARNINGS #includeint main() { int n = 10; int* pointer = &n; //pointer是指针变量,也就是我们所说的指针,存放a的地址 //应注意,&n是给pointer,而不是*pointer这一点很重要 }
我们该如何理解上面注释的那句话呢?
我们知道对于变量的访问有两种访问形式,一种是直接访问,一种是间接访问。
(1)直接访问就是直接按变量名进行访问,在此我们不进行过多叙述。
(2)我们看上面的代码例子,有2个变量,1个是n,1个是pointer,我们将n的地址传给pointer,此时pointer里边存储的是a的地址,我们可以对pointer进行解引用从而间接找到a。而这个过程便是间接访问。
通过上边介绍,我们对指针可以概括成两点:
(1):指针就是地址,是内存最小单元的编号
(2):指针变量是用来存放地址的变量
总之:指针就是它指向对象的地址,如上图所示,pointer存放i的地址,通过地址找到i的内容。
在上篇博客中提到sizeof()计算变量、数组等字节的大小,那指针变量有没有字节大小的限制呢?
答案毋庸置疑,指针也是存在字节大小的。
看下图:
在X86平台下,也就是32位编译环境下,指针大小是4个字节。这是因为在32位编译环境,地址是32个0或1组成,那么地址就是4个字节的空间来存储的,所以32位平台下指针是4个字节的大小。
按照上面所述,我们可以知道在X64平台下,指针的大小是8个字节。
对上面所介绍的进行总结:
(1):指针大小在32位平台是4个字节,在64位平台是8个字节
(2):指针变量是用来存放地址的,地址是唯一标示的一块地址空间
❗❗❗
在介绍本小节内容前,我们先看一个问题?
Question:
现存在int型变量a,和float型变量b,把他们先后分配在2000开始的存储单元中,&a和&b的信息完全相同吗?
答案是不相同的,C语言中地址包括位置信息该地址所指向的数据的类型信息,它们的存储单元编号虽然相同,但他们的数据类型不同。
3.1指针我们先看这样一段代码:
#define _CRT_SECURE_NO_WARNINGS #includeint main() { int n = 10; int m = 100; int* pointer_1,*pointer_2;//定义指向整形变量的指针变量 pointer_1 = &n;//将地址给指针变量 pointer_2 = &m; printf("n=%d m=%dn", n, m); printf("pointer_1=%d pointer_2=%dn", *pointer_1, *pointer_2); return 0; }
上面代码是一个基本的指针的定义、初始化,因为指针变量存储的内容是变量的地址,所以我们需要通过解引用操作符“*”进行访问。
请大家思考:如果我将一个int型变量地址分别赋给基类型为int和基类型为float的指针变量,这时会发生什么情况呢?
❗
看下面错误代码:
#define _CRT_SECURE_NO_WARNINGS #includeint main() { int a; int* p1; float* p2; p1 = &a;//right p2 = &a;//error return 0; }
我们需要明确:
在定义指针变量后,我们已经给出指针变量的基类型,那么这个指针变量也就只能指向与基类型一样的类型变量
int *pointer_1;//pointer_1是指向int型变量的指针变量 float *pointer_2;//pointer_2是指向float型变量的指针变量 char *pointer_3;//pointer_2是指向char型变量的指针变量
所以在这提出这样的概念:
一个变量的指针的含义包括:一是以存储单元编号表示的纯地址,二是它指向的数据类型。
这点务必明确!!!
我们了解到C语言有int、float、char、double等类型,同样指针也是有着诸如上面所提到的类型。
那么指针的类型是什么样子呢?该如何表示定义呢?
char* pa=NULL; int * pb=NULL; double* pc=NULL; short* pd=NULL; long* pe=NULL;
通过前面所提,pa、pb等是指针变量,那么它们的类型就是char*、int*等。
所以,指针的定义形式为:type+*
我们提出指针类型后,指针类型意义是什么呢?
3.2.1指针的±操作#define _CRT_SECURE_NO_WARNINGS #includeint main() { int n = 10; char* pc = (char*)&n; int* pi = &n; printf("&n=%pn", &n); printf("&pc=%pn", pc); printf("&(pc+1)=%pn", pc + 1); printf("&pi=%pn", pi); printf("&(pi+1)=%pn", pi+1); }
结果如图所示:
n的地址与pc、pi的内容一致,为0135FEC0,而在对pc+1、pi+1后它们所显示的结果(pc+1)=0135FEC1,(pi+1)=0135FEC4,这是因为pc是指向char类型的指针,而pi是指向int类型的指针,char类型在内存中占用1个字节,int占用4个字节。所以会出现上述情况。
对上述结果作以总结:
指针类型决定了指针进行±整数时移动距离有多大
#define _CRT_SECURE_NO_WARNINGS #includeint main() { int n = 0x11223344; char* pc = (char*)&n; int* pi = &n; *pc = 0; *pi = 0; return 0; }
因为需要调试观察内存变化,在此不方便进行展示,所以大家自行下去进行操作观察即可,在此直接给出结论。
对上述结果作以总结:
指针类型决定了指针解引用时可以操作几个字节
指针含义:
(1)以存储单元编号表示的纯地址
(2)指向的存储单元的数据类型
指针类型决定了:
(1)解引用时可以操作几个字节
(2)指针在进行整数运算时可以移动的距离有多大
概念:野指针就是指针指向的位置不可知的指针
野指针的成因一般有两种:
1.指针未初始化
//错误程序,此时指针为野指针 #define _CRT_SECURE_NO_WARNINGS #includeint main() { int* p; *p = 20; printf("%d", *p); }
2.指针越界访问
//当指针指向范围超过数组大小时,此时指针为野指针 #define _CRT_SECURE_NO_WARNINGS #includeint main() { int arr[10] = { 0 }; int* p = arr; for (int i = 0; i < 12; i++) { *(p++) = 1; } return 0; }
野指针一般情况也就是上面这些错误,在此也不进行过多赘述,大家在写代码时要正确使用指针,别让指针太“浪”!
5指针的运算通过前面的介绍,相信大家对指针是什么也有了一定的了解,接下来介绍指针的运算
5.1指针±整数看下面这段代码,引入指针±整数概念:
int main()
{
float arr[5];
float* vp;
for (vp = arr; vp < &arr[5];)
{
*vp++=0;
}
return 0;
}
通过调试我们不难得到:前面提到过,指针类型决定了指针±整数时可以移动多大距离,所以上面代码的指针vp每次移动4个字节,因为*操作符比++操作符优先级高,所以先进行解引用,然后++。
在前面我们了解了指针±整数,那么指针-指针有什么具体含义呢?我们看下面的一段代码:
#define _CRT_SECURE_NO_WARNINGS #include//实现计算字符串长度库函数strlen(),返回字符个数 int my_strlen(char* str) { char* p = str; while (*p != ' ') { p++; } return p - str; } int main() { char arr[10]; gets(arr);//输入字符串 int len = my_strlen(arr); printf("%d", len); return 0; }
在上篇数组博客中,我们了解到strlen()计算字符串中字符个数的库函数,上述代码我们实现my_strlen()函数。
char* p = str; 代表字符串中首元素地址
while (*p != ' ')
{
p++;
}
return p - str;
当字符等于' '时结束循环,此时p指向' '的地址;
所以p - st意味着' '的地址-首元素地址
所以,以上我们不难得出结论:指针-指针结果就是两个指针之间元素的个数。
5.3指针的关系运算在了解到指针±运算后,我们接下来看指针的关系运算
int arr[5];
int* vp;
for (vp = &arr[5]; vp >&arr[0];)
{
*--vp=0;
}
这个代码意义就是将arr数组每个元素初始化为0,看调试结果:
我们对上述代码进行修改:
int arr[5];
int* vp;
for(vp = &arr[4]; vp >=&arr[0];vp--)
{
*vp=0;
}
修改后代码与上面不同的是for循环部分,而此代码vpp最终指向arr数组的首元素的前一个元素的地址,从而越界访问
所以,我们对比两次调试结果,可以轻易看出vp的值第一次代码完成后是数组元素的首地址,第二次代码完成后vp的值是数组首元素的前一个元素的地址。
所以,C语言规定,允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
用图表示,也就是说,允许p1和p2进行比较,不允许p1和p3进行比较。
当然,上面所举的例子也都是反方向进行初始化,如果正方向初始化则不牵扯上面这些问题了,所以大家在利用指针对数组进行循环操作时,尽量选择正方向操作,这样的话,是没有任何问题的。
6指针和数组前面我们介绍了指针的一系列操作,那么数组和指针有什么关系呢?如何利用指针对数组进行操作呢?
int arr[5]={1,2,3,4,5};
for(int i=0;i<5;i++)
{
printf("%d",arr[i]);
}
这是对数组进行访问的操作,而在上一篇博客提到过,数组名称代表着首元素的地址,我们结合前面介绍的指针的±整数运算,对上面代码进行修改:
int arr[5]={1,2,3,4,5};
int *p=arr;
for(int i=0;i<5;i++)
{
printf("%d",*(p+i));
}
因为p此时为数组元素的首地址,通过+i之后解引用,我们依旧可以得到arr数组的元素值,而上述代码中p是arr首元素的地址,我们在对上述代码进行修改:
int arr[5]={1,2,3,4,5};
for(int i=0;i<5;i++)
{
printf("%d",*(arr+i));
}
此时依旧可以正常打印输出数组arr的元素值。
7二级指针在第一小节,我通过绘图的方式介绍了指针的概念,而二级指针是什么呢?举个例子,我们通过一张图来看一下:
我们知道,指针变量存储地址,所以pointer_1存储变量a的地址0x0018ff44,pointer_2存储pointer1的地址0x0018ff40。
看一段代码:
int a = 10;
int* pointer_1 = &a;//一级指针
int** pointer_2 = &pointer_1;//二级指针
**pointer2 = 30;
printf("%d", a);
因为ointer_1存储变量a的地址,pointer_2存储pointer_1的地址,所以对pointer_2通过2次解引用就可以找到a的值。第一次解引用pointer_2找到pointer_1中存储的值,也就是变量a的地址,第二次解引用就可以通过pointer_1中存储的地址找到a了。
其图形原理与上图一致,不清楚的大家可以画一画,在这不进行赘述。
8.指针数组在提及本小节前,有个问题,指针数组是指针还是数组?
答案是数组,数组里面存放的是指针!
我们直接看一段代码:
int main()
{
int a = 10;
int b = 20;
int c = 30;
int* arr[3]={ &a,&b,&c };
for (int i = 0; i < 3; i++)
{
printf("%d ", *(arr[i]));
}
}
我们建立指针数组arr,里面存放a,b,c的地址,通过循环遍历解引用即可正常访问其值,大家可以自行验证。
在上篇介绍数组的博客中,我们提到二维数组可以看成是一维数组的组合,所以我们接下来来看二维指针数组:
int arr1[4] = { 1,2,3,4 };
int arr2[4] = { 2,3,4,5 };
int arr3[4] = { 3,4,5,6 };
int* arr[3] = { arr1,arr2,arr3 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("n");
}
前面提到过,数组名称就是数组首元素的地址,所以在指针数组arr中存放三个数组首元素,即数组名称。我们在for循环当中,第一层循环调用指针数组arr元素,第二层循环调用调用指针数组元素的元素。
举个例子:第一次循环,在指针数组arr中先访问arr1,第二层循环则访问arr1[j]就等价于arr[0][j],这样相信大家也就理解了二维指针数组的应用,结合第6节内容则可以很好的理解本节所提到的指针数组的相关操作。
9const 修饰指针emmmm,本来这篇已经博客完成了,在后边例题中又涉及到const修饰指针这部分内容,所以只好又单独拉出来说一下。
我们知道const修饰常变量,这个变量既有常量的特征,又有变量的属性,那么当const修饰指针时指针又会有什么不一样的特征呢?
9.1const在*左边int main()
{
int n = 10;
const int* p = &n;
//修改指针变量指向对象的值
*p = 30;//err
int b = 20;
p = &b;//给指针变量重新赋值
}
我们不妨调试一下:
如图,所以说const在*左边,修饰的是指针指向的内容,而指针本身的内容可以改变。
int main()
{
int n = 10;
int* const p = &n;
*p = 30;
int b = 20;
p = &b;//err
}
继续调试:
我们可以看到,两次代码报错是一致的.
所以,const在*右边时,指针指向的内容可以被改变,但是指针本身的内容不可以被修改。
9.3总结根据上面两段代码,我们不难得到:
(1)const在左边时,修饰的是指针指向的内容,所以指针指向的内容不允许被修改,但是指针变量本身的内容可以被修改!
(2)const在右边时,修饰的是指针变量本身,所以指针指向的内容允许被修改,但是指针变量本身的内容不可以被修改!
在了解到上述所有内容以后,我们接着看两道例题,了解指针在具体如何使用,其基本用法有哪些。
10.1库函数strlen()的实现#includeint my_strlen(char* str) { int count = 0; while (*str != ' ') { str++; count++; } return count; } int main() { char str[100]; gets(str); int len=my_strlen(str); printf("len=%dn", len); return 0; }
在上一篇博客,我提到过strlen()定义及用法,所以在这不进行赘述。
代码分析:
在通篇内容中,我一直提到指针就是地址,数组名即是数组首元素的地址。在上述代码中,gets()用来输入字符串,函数形参是指针,用来接收数组首元素地址,当数组元素不等于’ ’时则一直进行循环,并对count进行++,当条件不满足时则跳出循环,从而返回count的值,也就是字符串的中字符的个数。
我们先了解strcpy(str1,str2)函数,strcpy(str1,str2)函数是将str2的内容拷贝到str1里面,有了这个概念,我们接下来实现strcpy(str1,str2)函数
方法一:
#includevoid my_strcpy(char* str1, const char* str2) { while (*str2 != 0) { *str1 = *str2; str2++; str1++; } *str1 = *str2; } int main() { char str1[100]; char str2[100]; gets(str1); gets(str2); my_strcpy(str1, str2); printf("%s", str1); }
代码分析:
main()函数中内容较为简单不进行过多介绍,我们直接来看my_strcpy()函数中的内容:
void my_strcpy(char* str1, const char* str2)
函数形参中,因为str2是const修饰的指针变量,而在上一小节我提到过,const在*左边时,修饰的是指针指向的内容,所以指针指向的内容不允许被修改,但是指针变量本身的内容可以被修改,也就是指针变量中存储的地址可以被改变。
while (*str2 != 0)
{
*str1 = *str2;
str2++;
str1++;
}
*str1 = *str2;
因为str2是指针变量,存储的是main函数中str2数组的首地址,所以对其解引用,当它的内容不等于’ ’时进行循环,不满足这个条件时,则退出循环。而在循环外有*str1 = str2;这样一条语句,就是为了防止字符串str1原本存储的内容长度大于str2的内容,复制过去后字符串不正确。因为str2最终是指向’ ’的,所以将结束标志也给str1时,也就保证了数据的准确性。
方法二:
void my_strcpy(char *str1,const char *str2)
{
while ((*str1++) = (*str2++))
//++优先级高于*
//因为是后置++,所以先使用当前值后在进行++
{
;
}
}
int main()
{
char str1[100];
char str2[100];
gets(str1);
gets(str2);
my_strcpy(str1, str2);
printf("%s", str1);
}
这个代码原理与上述一致,在这就不多进行介绍。
❗❗❗
(1).在上述代码中,str1和str2数组长度我定义为100,所以在输入时字符串长度不能超过其长度减1,因为字符串结束标志是’ ’,所以最多输入的字符个数是长度减1。
(2).也不能将长度后者比前者大的字符串复制给前者,这点大家可以上机自行验证。
内容到这里也就全部结束了,也写了1w多字,因为指针的内容在刚开始了解的时候比较晦涩难懂,所以可能大家在看后边两道例题时比较困难,我建议大家要着重看一下指针类型以及定义等内容,思考在习题例子当中指针++,改变指针变量本身内容时指针跳过了多少字节?指针在被其他类型操作时内存中字节又是如何变化的?这些内容我在本篇博客中提及到过,但是没有深入讨论,铁子们可以上机自行验证一下。本次只是初步了解指针,后续还会介绍具体指针在函数中传参、应用,以及指针数组的深入等,有问题的话大家可以评论区留言,或者私信我!
好了,本次指针的内容到这就结束了,创作不易,大家点个,加个收藏在划走呗。



