在最近学习了C语言中关于指针的一些基础知识,现在分享给大家。
指针是什么?
我们的计算机都有着内存空间,内存空间可以被切割成一个个1byte的内存的单元。
理解指针的两个要点:
1.指针是内存中最小单元的编号,也就是地址
2.平时口语中的指针,通常就是指的指针变量,用来存放内存地址的变量
指针变量的简单例子:
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量
中,p就是一个之指针变量。
return 0;
}
指针变量存放的就是地址,通过地址就可以找到一个内存单元。
指针的大小在32位上是4个字节;在64位上是8个字节。
对于32位的机器,假设有32根地址线,那么每根地址线在寻址的时候产生高电平和低电平即0和1;
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
...
...
11111111 11111111 11111111 11111111
这里一共有2^32个地址,每个地址有32个bit位,再换算得到4个字节,也就是一个地址在32位的机器上是占4个字节。
同样,在64位的机器上也可以得出一个地址占8个字节。
指针与指针类型
指针和其他的数据类型一样也是有类型的。例如int*、char*、float*...
在这里先引一个小例子:
#include <stdio.h>
int main(void)
{
char* pc = NULL;
short* ps = NULL;
int* pi = NULL;
double* pd = NULL;
printf("%d\n", sizeof(pc));//4
printf("%d\n", sizeof(ps));//4
printf("%d\n", sizeof(pi));//4
printf("%d\n", sizeof(pd));//4
return 0;
}
最终得到的结果,都是4,说明不同类型的指针所占的空间大小是一致的,那么指针的类型又有什么意义呢。
指针的类型差异决定了指针在被解引用的时候访问几个字节
int* 4个字节 char* 1个字节
指针的类型决定了指针的步长。
指针类型可以决定,以什么样的方式来访问内存
可以使用代码来简单地调试一下:
#include <stdio.h>
int main(void)
{
int a = 0;
int* pa = &a;
float* pf = &a;
*pa = 100;
*pf = 100;
//指针的步长
int a = 0x11223344;
int* pa = &a;
char* pc = (char*)&a;
printf("pa = %p\n", pa);
printf("pa+1 = %p\n", pa+1);
printf("pc = %p\n", pc);
printf("pc+1 = %p\n", pc+1);
int a = 0x11223344;
int* pa = &a;
*pa = 0;
int b = 0x44332211;
char*pb = (char*)&b;
*pb = 0;
return 0;
}
下图就是调试的部分结果,左图为中间部分程序的截图,int*和char*的地址是初始地址是相同的,但是他们的下一步的地址不同;右图是下部分代码的调试截图,表明了指针类型可以决定,以什么样的方式来访问内存。
关于int和float类型的数据都是四个字节的大小,那么他们的指针类型有什么区别的,现在简要的来说就是浮点类型的数据和整数类型的数据在内现中存放的方式不相同。
野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针的成因
1.指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
例如:指针指向数组时,当指针的指向范围超出数组的范围时,指针就变为野指针
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
*(p++) = i;
}
return 0;
}
3.指针指向的空间释放
#include <stdio.h>
int* test()
{
int a = 10;
return &a;//空间销毁了
}
int main(void)
{
int* p = test();//通过非法地址还能找到内现存中的空间
printf("hehe\n");
if (p != NULL)
{
printf("%d\n", p);//无效判断
}//每次出来的结果都不相同
return 0;
}
虽然能够打印出数据,但是这些数据的值都是随机的。
那么就需要我们来规避野指针:
int* p2 = NULL;
*p2 = 100;//err
int* p3 = NULL;
if (p3 != NULL)
{
*p3 = 100;//ok
}
指针运算
指针+-整数
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;//这里的解释就是 *vp++ 等价于 *vp 和 vp++
}
指针的关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
//以上的代码可以进行简化
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
在绝大部分的编译器上简化的代码可以运行,但是我们应该避免这样写,因为标准并不保证 它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
指针-指针
指针也可以和指针进行相减的操作:
#include <stdio.h>
#include <string.h>
int my_strlen(char* str)
{
char* fir = str;
//int count = 0;
while (*(str) != '\0')
{
str++;
//count++;
}
return (str - fir);
//return count;
}
int main(void)
{
//int arr[10] = { 0 };
//printf("%d\n", &arr[9] - &arr[0]);
int len = my_strlen("abcdref");
printf("%d\n", len);
return 0;
}
注:
指针减去指针的绝对值得到的是指针和指针之间的个数
不是所有的指针都可以相减
前提:指针指向同一块空间
指针和数组
先举一个例子:
#include <stdio.h>
int main(void)
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++)
{
arr[i] = 1;
}
int* p = arr;
for (int i = 0; i < sz; i++)
{
*p++ = 1;
}
int* p = arr;
for (int i = 0; i < sz; i++)
{
*(p + i) = 1;
}
return 0;
}
以上代码的三种写法都可以正常的对数组进行赋值操作。数组名就是首元素的地址,这样就与指针变量取得联系。
为了更加清晰的看到这三者之间的关系:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main(void)
{
//数组通过指针来访问
int arr[10] = { 0 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//for (int i = 0; i < sz; i++)
//{
// printf("%d\n", *(p+i));//或者可以写成*(arr + i)
//}
for (int i = 0; i < sz; i++)
{
printf("%p==%p==%p\n", &arr[i], p + i, arr + i);
}
return 0;
}
左图就显示了三者是相同的。
二级指针
指针变量也是变量,是变量就有地址,我们同样可以使用一个指针来接受它的地址。
二级指针是用于存放一级指针变量地址的
#include <stdio.h>
//二级指针
int main(void)
{
int a = 10;
int* pa = &a;//pa为指针变量,一级指针
int** ppa = &pa;//ppa为二级指针变量
printf("**ppa = %d\n", **ppa);
//int * pa *说明pa为指针 int说明pa指向的对象为int类型
//int* * ppa *说明ppa为指针 int*说明pa指向的对象为int*类型
//*pa = 10;
//printf("*pa = %d\n", *pa);
return 0;
}
指针数组:存放指针的数组
我么可以使用指针数组来实现一个二维数组:
#include <stdio.h>
int main(void)
{
//使用指针数组编写二位数组
int arr1[4] = { 1,2,3,4 };
int arr2[4] = { 2,3,4,5 };
int arr3[4] = { 3,4,5,6 };
int* parr[3] = { arr1,arr2,arr3 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
结构体
结构体用于描述复杂对象。
嵌套结构体的初始化:{{xxx,xxx,xxx},xxx,xxx}
如果没有全部赋值称为不完全初始化
结构体内容比较简单,就直接放一个例子:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct Peo
{
char name[10];
char num[15];
char sex[5];
int heigh;
};
struct St
{
struct Peo p;
int num;
float f;
};
void Print(struct Peo* p1)
{
printf("%s %s %s %d\n", p1->name, p1->num, p1->sex, p1->heigh);
}
int main(void)
{
struct Peo p1 = { "张三","13624157884","男",181 };
struct St s = { {"王五","12345968512","女",176},15, 6.65 };
printf("%s %s %s %d\n",p1.name,p1.num,p1.sex,p1.heigh);
printf("%s %s %s %d %d %f\n",s.p.name,s.p.num,s.p.sex,s.p.heigh,s.num,s.f);
Print(&p1);
return 0;
}
建议使用函数进行结构体传参时最好使用指针的形式,可以节约内存。