初识指针(1)

1.内存和地址

为了有效管理内存空间 ,将内存划分为一个个的内存单元,每个内存单元取一个字节

常见单位(补充)

1byte=8bit
1KB=1024byte
1MB=1024KB
1GB=1024MB
1TB=1024MB
1PB=1024TB

在计算机中中,我们也把内存的编号叫做地址,C语言中,地址也被称为“指针”

可以理解为:内存的编号==地址==指针

2.指针变量和地址

2.1取地址操作符(&)

在C语言中创建变量就是向内存中申请空间

上述代码就是创建了整型变量啊,内存中申请了4个字节的空间用于存放10

每个字节都有地址

int main()
{
	int a = 10;

	return 0;

}
0xFFD70
0xFFD71
0xFFD72
0xFFD73

&就是用来得到a地址的操作符

int main()
{
	int a = 10;
	&a;
		printf("%p\n",&a);
	return 0;

}

上述代码会打印处理:012FF89C

2.2指针变量和解引用操作符(*)

2.2.1指针变量

我们通过取地址操作符得到的地址,有时候会后期使用,为了方便后期使用,应该存储起来,我们会把地址值存放在指针变量中

2.2.2如何拆解指针类型
int a=10;
int * pa=&a;

*是说明pa是指针变量,int说明pa指向的是整型

char ch=‘w’;
char pc=&ch;

char类型也同理

2.2.3解引用操作符

我们保存了地址,如何使用呢

在C语言中,我们只要拿到了地址就可以通过地址找到地址所指向的对象

这时需要运用到解引用操作符(*)

int main()
{
	int a = 10;
	int *p=&a;
	*p = 0;
	return 0;

}

*p的意思就是通过p中存放的地址找到所指向的空间,*p就是a;这个操作就是将a变量改为0;

2.3指针变量的大小

int main()
{
	printf("%zd", sizeof(char*));
	printf("%zd", sizeof(int*));
	printf("%zd", sizeof(short*));
	printf("%zd", sizeof(double*));
	return 0;
}

在32位平台下是32个bite位,指针变量大小是4个字节

在64位平台下是64个bite位,指针变量大小是8个字节

注意:指针变量的大小与类型无关,在相同平台下,大小都是相同的

3.指针变量类型的意义

3.1指针的解引用

int main()
{
	int n = 0x11223344;
	int* pi = &n;
	*pi = 0;
	return 0;

}
int main()
{
	char n = 0x11223344;
	char* pi = &n;
	*pi = 0;
	return 0;

}

通过调试可以看到,代码1将n的4个字节全部改为0;代码2将n的第一个字节改为0;

结论:指针类型决定了对指针解引用的权限(一次可以操作多少个字节)

比如:char类型就只能操作一个字节,而int类型能操作4个字节

3.2指针+-整数

int main()
{
	char n = 0x11223344;
	char* pi = &n;
	int* pc = &n;
	printf("%p\n", &n);
	printf("%p\n",pi);
	printf("%p\n",pi+1);
	printf("%p\n", pc);
	printf("%p\n",pc+1);


	return 

我们可以得出:指针的的类型决定了指针向前或向后走的距离

char类型的指针变量+1只跳过了1个字节;而int类型的指针变量跳过了4个指针类型

3.3void*指针

在指针类型中有一种叫void*类型的指针,是无具体类型的指针(泛型指针);这种指针可以接受任何类型的指针,但不能进行指针+-整数和指针解引用的运算

在上述代码中 char类型的变量的地址赋给int ,编译器给出不兼容的警告,如果用void*就不会出现警告

我们可以看到,void*类型可以接受不同类型的地址,但不能进行解引用运算

一般void*类型用来函数参数部分,用来接受不同类型的地址,可以实现泛型编程的效果,使得一个函数来处理多种类型的数据。

4.const修饰指针

4.1const修饰变量

变量是可以修改的,把变量地址交给一个指针变量,通过指针变量也可以修改,如果不想变量被修改,应该加以限制

上述代码中 n是不可以被修改的,被const修饰后 ,在语法上发生了限制,但是可以通过n的地址去修改。

4.2const修饰指针变量

一般来说const修饰指针变量,可以放在左边也可以放在右边,但是意义是不一样的。

int *p;
int const  *  p;
int * const   p;
void test1()
{
	int m = 0;
	int n = 0;
	int* p = &n;
	*p = 10;
	p = &m;
}
void test2()
{
	int m = 0;
	int n = 0;
	int const* p = &n;
	*p = 10;
	p = &m;
}
void test3()
{
	int m = 0;
	int n = 0;
	int* constp = &n;
	*p = 10;
	p = &m;
}
void test4()
{
	int m = 0;
	int n = 0;
	int const* const p = &n;
	*p = 10;
	p = &m;
}
int main()
{
	test1();
	test2();
	test3();
	test4();


}

结论:const修饰指针变量时,const放在*左边,修饰的是指针指向的内容,保证指针指向的内容不会改变,但是指针变量本身的内容可以改变,const放在*右边,修饰的是指针变量本身,保证指针变量的内容不会改变,但是指针指向的内容可以改变。

5.指针运算

5.1指针+-整数运算

数组在内存中是连续存放的,只要知道第一个元素的地址,就能找到其他元素。

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,10,9};
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz;i++)
	{
		printf("%d\n",*(p+i));

	}
	return 0;
}

5.2指针-指针

int my_strlen(char* s)
{
	char* p = s;
	while(*p!='\0')
		p++;
	return p - s;
}
int main()
{
	printf("%d\n", my_strlen("abc"));
	return 0;
}

5.3指针的关系运算

int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < arr + sz)
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

6.野指针

概念:野指针就是指针指向的位置是不可知的

6.1野指针成因

1.指针未初始化

int main()
{
	int* p;
	*p = 20;
	return 0;

}

2.指针越界访问

int main()
{
	int arr[10] = {0};
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		*(p++) = i;
	}
	return 0;
}

3。指针指向空间释放

int* test()
{
	int n = 100;
	return &n;
}
int main()
{
	int* p = test();
	printf("%d\n", *p);
	return 0;
}

6.2如何规避野指针

6.2.1指针初始化

如果明确知道指针指向哪里就直接赋值地址,如果不知道就可以给指针赋值NULL;NULL是C语言中定义的一个标识符常量,值是0;0也是地址,但是这个地址无法使用,读写该地址会报错。

int main()
{
	int num = 10;
	int* p = &num;
	int* c = NULL;
	return 0;
}

6.2.2小心指针越界

一个内存申请了哪些空间,通过指针就会访问哪些空间,不能超出范围

6.2.3指针不再使用时,及时置NULL,指针使用之前检查其有效性

当指针变量指向一块区域时,我们可以通过指针访问该区域后期不再使用这个指针时,将该指针置为NULL.

int main()
{
	int arr[10] = {0};
	int* p = arr;
	int i = 0;
	for (i = 0; i <= 11; i++)
	{
		*(p++) = i;
	}
	p = NULL;
	p = &arr[0];
	if (p != NULL)
	{

	}
	return 0;
}

6.2.4避免返回局部变量的地址

7.assert断言

assert.h头文件定义了宏assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止,这个宏通常被叫做”断言“

assert(p!=NULL);

上述代码如果确实不等于,代码继续运行,否则就会报错终止。

assert 在确定已经不需要断言时可以在#include<assert.h>前面加上NDEBUG

如果程序再一次出现问题,将NDEBUG注释掉。

8.指针的使用和传址使用

8.1strlen的模拟实现

库函数strlen的功能时求字符串长度

int my_strlen(const char * s)
{
	int c = 0;
	assert(s);
		while (*s)
		{
			c++;
			s++;
		}
	return c;
}
int main()
{
	int len = my_strlen("abdsjjdvkds");
	primtf("%d\n", len);
	return 0;
}

8.2传值调用和传址调用

void sw(int x, int y)
{
	int tmp = x;
	x = y;
	y = tmp;

}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);
	printf("%d\n",a,b);
	sw();
		printf("%d\n",a,b);
		return 0;
}

这种调用方式叫传值调用

sw中x和y的值并不会影响主函数中a和b的值的变化

结论:实参传给形参时,形参会单独创建一个临时空间来接收实参,对形参的修改不影响实参。

void Swap(int *px, int *py)
{
	int tmp = 0;
	tmp = *px;
	*px = *py;
	*py = tmp;


}
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d",&a,&b);
	printf("a=%d\n b=%d\n",a,b);
	Swap(&a,&b);
		printf("a=%d\n b=%d\n",a,b);
		return 0;
}

这种函数调用叫传址调用

传址调用可以让函数与主调函数直接产生真正的联系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值