目录
Ⅰ、指针的类型决定了,对指针解引用的时候有多大权限(一次可以操作几个字节)
一、指针变量和地址
1、地址
在C语言中,地址==指针
1.1 取地址操作符--&
&
#include<stdio.h>
int main()
{
int a = 10;
&a; //取出a的地址
printf("%p \n",&a);
//%p是打印地址的
return 0;
}
1.2 解引用操作符--*
我们通过上面的取地址操作符(&)拿到的地址是一个数值【000000EEBB94F5C4】,这个数值需要被储存起来,方便后期使用,我们将该数值存放在指针变量中。
【需要注意的是取地址操作符,拿到的是变量第一个字节的地址】
【int 型】
#include<stdio.h>
int main()
{
int a = 10;
int * pa = &a; //取出a的地址并且存放在指针变量pa里
//pa是指针变量的名字
//【int *】是pa的类型
return 0;
}
int a = 10;
int * pa = &a;
pa的左边写的是int * , *表示pa是指针变量,前面的 int 表示pa指向的是整型(int)类型的对象。
【char 型】
char ch = 'w';
char * pc = &ch;
【指针变量是一种变量,用来存放地址】
1.3 指针p的三个相关值
[1] p , p里面放着一个地址
[2]*p , p指向的那个对象
[3]&p , 表示的是变量p的地址
2、指针变量
2.1 指针变量的大小
一个地址需要32个bit位,需要4个字节储存。(1个字节=8个bit位)
sizeof :求某个类型创建变量占内存空间的大小
#include<stdio.h>
int main()
{
printf("%zd\n",sizeof(char *)); //4(x86) or 8(x64)
printf("%zd\n",sizeof(short *)); //4(x86) or 8(x64)
printf("%zd\n",sizeof(int *)); //4(x86) or 8(x64)
printf("%zd\n",sizeof(double *)); //4(x86) or 8(x64)
return 0;
}
//指针变量大小取决于地址的大小
//32位平台下地址是32个bit位(4个字节) ---x86环境下
//64位平台下地址是64个bit位(8个字节) ---x64环境下
【指针变量的大小与类型无关,与平台有关(x86:4 ---x64:8)】
2.2 指针的类型
Ⅰ、指针的类型决定了,对指针解引用的时候有多大权限(一次可以操作几个字节)
【eg: char* 的指针解引用只能访问1个字节
int* 的指针解引用可以访问4个字节】
Ⅱ、指针的类型决定了指针向前或向后走一步的距离
#include<stdio.h>
int main()
{
int a = 0x11223344;
int * pa = &a;
char * pc = &a;
printf("&a=%p\n",&a); //&a=0136FCF0
printf("&a+1=%p\n", &a + 1); //&a+1=0136FCF4
printf("pa=%p\n",pa); //pa=0136FCF0
printf("pa+1=%p\n",pa+1); //pa+1=0136FCF4
printf("pc=%p\n",pc); //pc=0136FCF0
printf("pc+1=%p\n",pc+1); //pc+1=0136FCF1
return 0;
}
int*类型的指针变量+1跳过4个字节,char*类型的指针变量+1跳过1个字节.
Ⅲ、void*指针
可以把void*理解为无具体类型的指针(或叫泛型指针),这类指针可以用来接受任意类型地址,但这类型的指针不能直接进行指针的+-整数和解引用的运算。
当我们将一个int类型的变量的地址 赋值给 一个char*类型的指针变量时,编译器会给出警告, 因为类型不兼容,但使用void*就可避免。
#include<stdio.h>
int main()
{
int a = 10;
void* pa = &a;
void* pc = &a;
*p = 10;
*pc = 0;
return 0;
}
2.3 const 修饰指针
int const * p; //const 放在*的左边
//左边:
//限制了指针指向的内容,不能修改它指向的那个变量里面的内容
//但是指针变量本身可以改变,它可以改变指向(指向别的变量)
int * const p; //const 放在*的右边
//右边:
//限制的是指针变量本身,不能改变它的指向
//但是可以修改指针指向的那个变量里面的内容
#include<stdio.h>
void tset1()
{ //左边
int n = 10;
int m = 20;
const int * p = &n;
*p = 20; //不可以
p = &m; //可以
}
void tset2()
{ //右边
int n = 10;
int m = 20;
int * const p = &n;
*p = 20; //可以
p = &m; //不可以
}
void test3()
{ //左右两边都有
int n = 10;
int m = 20;
int * const p = &n;
*p = 20; //不可以
p = &m; //不可以
}
2.4 指针的运算
Ⅰ、指针 +- 整数
数组在内存中是连续存放的,只要知道第一个元素的位置,就可以顺藤摸瓜找到后面的所有元素。
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int * p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for( i=0 ; i<sz ; i++)
{
printf("%d ", *(p+i) );
//这里的p+i就是指针+整数
//int * p;
//p+i 是跳过i*sizeof(int)个字节
}
return 0;
}
Ⅱ、指针-指针
指针-指针的绝对值是指针和指针之间元素的个数。
指针-指针,计算前提,它们指向同一个空间。
【写一个函数,求字符串长度】
#include<stdio.h>
#include<string.h>
size_t my_strlen(char* p)
{
char * start = p;
char * end = p;
while(*end != '\0')
{
end++;
}
return end-start;
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%zd\n",len); //6
return 0;
}
2.5 野指针
野指针就是指针指向的位置是不可知的(随机的、不确定的、没有明确限制的)
Ⅰ、野指针的成因
(1.1) 指针未初始化
#include<stdio.h>
int main()
{
int *p; //局部变量指针未初始化,默认为随机值
*p = 20; //解引用操作符会形成非法访问
//此时p是野指针
return 0;
}
(1.2) 指针越界访问
#include<stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0 ; i<=11 ; i++)
{ //当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
(1.3) 指针指向的空间释放
#include<stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int *p = test();
printf("%d\n",*p);
return 0;
}
Ⅱ、如何规避野指针
(2.1) 指针初始化
如果明确知道指针指向哪里,就直接赋值相应的地址;如果不确定,就可以给指针赋值NULL。
【NULL是C语言中定义的一个标识符常量,值是0,0也是地址,但这个地址无法使用,读写它会报错。】
#include<stdio.h>
int main()
{
int num = 10;
int *p1 = #
int *p2 = NULL;
return 0;
}
(2.2) 使用前检查
【指针变量不再使用时,及时置NULL,指针使用之前检查有效性。】
当指针指向一块区域时,我们可以通过指针访问;后期不使用这个指针访问时,可以把指针置NULL。
只要是NULL指针就不去访问,同时使用指针前,要判断指针是否为NULL。
【if(p != NULL) { };】
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
for( i=0 ; i<10 ; i++ )
{
*(p++) = i;
}
//此时p已经越界,可以把它置为NULL
p = NULL;
//下次要使用它,先判断p不为NULL再使用
p = &arr[0];
//进行判断
if(p != NULL)
{
//....
}
return 0;
}
(2.3) 指针使用前检查有效性
Ⅲ、assert断言


2.6 指针的使用
例题(strlen的模拟实现)
#include<stdio.h>
#include<assert.h>
#include<string.h>
size_t my_strlen(const char* s)
{
size_t count = 0;
assert(s != NULL); //监测指针s是否有效
while(*s)
{
count++;
s++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%zd\n",len); //6
printf("%s\n",arr); //abcdef
}
2.7 传址调用
如果我们想写一个函数,交换两个整型变量的值
#include <stdio.h>
void Swap1(int x, int y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b); //10 20
printf("交换前:a=%d b=%d\n", a, b); //交换前:a=10 b=20
Swap1(a, b);
printf("交换后:a=%d b=%d\n", a, b); //交换后:a=10 b=20
return 0;
}
Swap1函数内部创建了形参x和y来接收a和b的值,
x和y确实收到了a和b的值,但x与a的地址不一样,b与y的地址也不一样,
相当于x和y是独立的空间,在Swap1函数内部交换x和y的值不会影响a和b。
Swap1函数在使用的时候,把变量本身直接传给了函数,这种调用函数的方式叫:传值调用。
2.8 传值调用
#include <stdio.h>
void Swap2(int *pa, int *pb)
{
int z = *pa; //z=a;
*pa = *pb; //a=b;
*pb = z; //b=z;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b); //3 5
printf("交换前:a=%d b=%d\n", a, b); //a=3 b=5
Swap2(&a, &b);
printf("交换后:a=%d b=%d\n", a, b); //a=5 b=3
return 0;
}
这里调用Swap2函数时,是将变量的地址传递给函数,这种函数调用方式叫:传址调用。
到这里指针理解系列1就结束啦!(未完待续哦)