C学习笔记——(6)系统性的介绍C语言中的指针操作

1、指针基础知识

1.1 指针和指针变量的区别

在这里插入图片描述

1.2 指针变量的定义和使用

在这里插入图片描述

指针变量是一个无符号的整型数。

下面是一个例子:

#include <stdio.h>

int main()
{
	int a = 0;
	char b = 100;
	printf("%p, %p\n", &a, &b); //打印a, b的地址

	//int *代表是一种数据类型,int*指针类型,p才是变量名
	//定义了一个指针类型的变量,可以指向一个int类型变量的地址
	int *p;
	p = &a;//将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
	printf("%d\n", *p);//p指向了a的地址,*p就是a的值

	char *p1 = &b;
	printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值

	return 0;
}

注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。

1.3 通过指针间接修改变量的值

int main() {
	int a = 20;
	int* p;  //指针类型的变量,存储变量的地址

	p = &a;  //把变量a对应的地址赋值给指针变量

	//下面两个结果相同
	printf("%p\n", &a);
	printf("%p\n", p);

	*p = 100;  //改变指针变量(地址)对应的值
    
    //下面打印内容相同,都是改变后的值=100
 	printf("%d\n", *p);  //就是将指针变量值(也就是地址)对应的内容打印出来
	printf("%d\n", a);

	return 0;
}

1.4 指针大小

在这里插入图片描述

note1:

  • &操作符是取变量的地址,是升维的;
  • *操作符是将地址对应的内容取出来,是降维的。

note2:
这里还要注意,前面说过指针变量是一个无符号的整型数,那么为什么还会有不同类型的指针变量呢?

看个例子:

int main() {
	char a = 100;  //ASCII码值100=d
	int* p;
	p = &a;
	printf("%d\n", *p);  //打印ASCII码值,出错了

	return 0;
}

上面的程序运行发现,打印的并不是ASCII码值100,但是当把指针变量定义成char类型后,就正确了。

答:
单独看指针变量确实没什么必要,但是当用指针变量来取对应的值时,这个时候就要对应,不然会出错的。

说了半天,就是说,定义指针变量的时候,类型要和变量的类型对应上。

1.5 野指针和空指针

1.5.1 野指针

指针变量也是变量,是变量就可以任意赋值,不要越界即可(32位为4字节,64位为8字节),但是,任意数值赋值给指针变量没有意义,因为这样的指针就成了野指针,此指针指向的区域是未知(操作系统不允许操作此指针指向的内存区域)。所以,野指针不会直接引发错误,操作野指针指向的内存区域才会出问题。

例子:

#include <stdio.h>

int main() {
	int a = 100;
	int* p;
	p = a; //把a的值赋值给指针变量p,p为野指针, ok,不会有问题,但没有意义

	p = 0x12345678; //给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义

	*p = 1000;  //操作野指针指向未知区域,内存出问题,err

	return 0;
}

1.5.2 空指针

野指针和有效指针变量保存的都是数值,为了标志此指针变量没有指向任何变量(空闲可用),C语言中,可以把NULL赋值给此指针,这样就标志此指针为空指针,没有任何指针。

例子:

int main() {
	//空指针是指内存地址编号为0的空间
	int* p = NULL;

	*p = 100;  //操作空指针对应的空间一定会报错
    printf("%d\n", *p);  

	//空指针可以用来条件判断

	return 0;
}

1.6 万能指针

void *指针可以指向任意变量的内存空间。注意:通过万能指针是不是直接修改变量的值的,必须要确定指针的类型才能操作。

例子:

#include <stdio.h>

int main() {
	int a = 10;
	void* p;

	//万能指针可以接收任意类型变量的内存地址
	p = &a;  

    //在通过万能指针修改变量的值时,需要找到变量对应的指针类型
	//*p = 100;  //err

	printf("%d\n", sizeof(p));  //万能指针占的字节大小:4

	return 0;
}

1.7 const修饰的指针变量

前面说过,const表示的是常量,修饰指针也是一样的,这个时候就是指针常量,这个指针常量本身的值是可以修改的,但是指向的值是不能变的。

例子:

#include <stdio.h>

int main() {
	int a = 10;
	int b = 20;
	const int* p = &a;  //指针常量
	p = &b;  //本身的值可以修改
	//*p = b;  //这个就会报错,因为这个p是指针常量,指向的值是不能修改的

	printf("%d\n", *p);  // 20

	return 0;
}

2、指针和数组

2.1 数组名

数组名字是数组的首元素地址,是一个常量。

例子:

	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	printf("a = %p\n", a);  //a本身表示第一个元素的地址
	printf("&a[0] = %p\n", &a[0]);

	//a = 10; //err, 数组名只是常量,不能修改

2.2 指针操作数组元素

由于数组名就是数组首元素的地址,那么把这个地址给一个指针变量,那操作这个指针变量也同样能改变数组。下面来看:

例子:

#include <stdio.h>

int  main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int i = 0;
	int n = sizeof(a) / sizeof(a[0]);

	for (i = 0; i < n; i++)
	{
		//printf("%d, ", a[i]);
		printf("%d, ", *(a + i)); //与上面等价,这个对地址加1,其实加类型的占用地址,这里是4
	}
	printf("\n");

	int* p = a; //定义一个指针变量保存a的地址
	for (i = 0; i < n; i++)
	{
		p[i] = 2 * i;  //操作指针,同样改变数组
	}

	for (i = 0; i < n; i++)  //验证,确实改变了
	{
		//printf("%d, ", p[i]);  //方式一
		printf("%d, ", *(p + i));  //方式二
	}
	printf("\n");

	return 0;
}

注意:

  • 指针计算不是简单的整数相加
  • 如果是一个int *,+1的结果是增加一个int的大小
  • 如果是一个char *,+1的结果是增加一个char大小

2.3 指针数组

指针数组,它是数组,数组的每个元素都是指针类型。

例子:

#include <stdio.h>

int main()
{
	//指针数组
	int* p[3];  //指针数组,里面的每一个元素都是指针类型
	int a = 1;
	int b = 2;
	int c = 3;
	int i = 0;

	p[0] = &a;  //将变量的地址给指针数组的每一个元素
	p[1] = &b;
	p[2] = &c;

	for (i = 0; i < sizeof(p) / sizeof(p[0]); i++)
	{
		printf("%d, ", *(p[i]));
	}
	printf("\n");

	return 0;
}

2.4 多级指针

  • C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
  • 二级指针就是指向一个一级指针变量地址的指针。
#include <stdio.h>

int main() {
	int a = 10;
	int* p = &a; //一级指针
	*p = 100; //*p就是a

	int** q = &p;
	//*q就是p
	//**q就是a

	int*** t = &q;
	//*t就是q
	//**t就是p
	//***t就是a

	return 0;
}

3、指针和函数

3.1 函数形参改变实参的值

若使用指针作为函数的形参,那么会改变实参的值。

例子:

#include <stdio.h>

void swap1(int x, int y)  // 形参的改变不会影响实参
{
	int tmp;
	tmp = x;
	x = y;
	y = tmp;
	//printf("x = %d, y = %d\n", x, y);
}

void swap2(int* x, int* y)  //函数形参改变会影响实参的值
{
	int tmp;
	tmp = *x;
	*x = *y;
	*y = tmp;
}

int main()
{
	int a = 3;
	int b = 5;
	swap1(a, b); //值传递
	printf("a = %d, b = %d\n", a, b);

	a = 3;
	b = 5;
	swap2(&a, &b); //地址传递
	printf("a2 = %d, b2 = %d\n", a, b);  //5 3

	return 0;
}

3.2 数组名做函数参数

数组名做函数参数,函数的形参会退化为指针:

#include <stdio.h>

void printArrary(int* a, int n)  //因为数组名做函数参数,会退化为指针,所以后面的n必须要传
{
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d, ", a[i]);
	}
	printf("\n");
}

int main()
{
	int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int n = sizeof(a) / sizeof(a[0]);  //数组的长度

	//数组名做函数参数
	printArrary(a, n);
	return 0;
}

3.3 指针做为函数的返回值

例子1:在字符串中查找第一次出现的字符

代码:

#include <stdio.h>

//在字符串中查找第一次出现的字符

char* my_strchar01(char* str, char ch) {//方法一:使用下标查找
	int i = 0;
	while (str[i]) {
		if (str[i] == ch) {
			return &str[i];//返回查找到字符的指针
		}
		i++;
	}
	return NULL;//如果没找到,返回NULL,即空指针
}

char* my_strchar02(char* str, char ch) {//方法二:使用数组名作为指针查找
	while (*str) {
		if (*str == ch) {
			return str;//返回当前地址
		}
		str++;
	}
	return NULL;
}

int main() {
	char str[] = "hello world";
	//char*p = my_strchar01(str, 'l');
	char*p = my_strchar02(str, 'l');

	if (p != NULL) {
		printf("查找成功,值为:%s", p);  //查找成功,值为:llo world
	}
	else {
		printf("查找失败!");
	}
	return 0;
}

例子2:从字符串中查找子串。

思想:定义三个指针,如下图所示,最上面的一个指针是遍历源字符串的指针,中间的一个是记录相同字符串的首地址,最下面一个是遍历子串的指针
在这里插入图片描述
然后让上下两个指针遍历源字符串和子串,若不相等,则把中间指针的下一个位置设置为源字符串的指针,依次类推。

代码:

#include <stdio.h>

char* my_strstr(char* str1, char* str2) {
	char* fstr1 = str1;//遍历源字符串的指针
	char* rstr1 = str1;//记录相同字符串的首地址
	char* tstr2 = str2;//遍历子串的指针
	while (*str1) {
		rstr1 = fstr1;
		while (*fstr1 == *tstr2 && *fstr1!='\0') {//对比字符串和子串中的字符
			fstr1++;
			tstr2++;
		}
		if (*tstr2=='\0') {
			return rstr1;//若把子串遍历完了,说明子串在原串中,返回记录相同字符串指针,也就是第一个
		}
		//回滚
		tstr2 = str2;//不相等,子串指针回到首地址
		fstr1 = rstr1 + 1;//遍历源字符串的指针向后走一个
	}
	return NULL;//空指针
}

int main()
{
	char str1[] = "helle world";
	char str2[] = "llo";
	char* p = my_strstr(str1, str2);
	printf("%s\n", p);//llo world
	return 0;
}

4、指针和字符串

4.1字符指针

代码:

#include <stdio.h>

int main()
{
	char str[] = "hello world";
	char* p = str;
	*p = 'm';
	p++;
	*p = 'i';
	printf("%s\n", str);//millo world,说明能修改字符数组的值

	p = "mike jiang";
	//*p = "dddd"; //错误,说明不能修改指针字符串的值
	printf("%s\n", p);//mike jiang

	return 0;
}

结论是:通过指针修改字符串的值,只能定义一个字符串数组,然后让指针指向它,就可以通过指针修改了。如果直接定义一个指针类型的字符串,就不能修改里面的值。

4.2 字符指针做函数参数

c语言没有字符串类型,所以要定义一个字符串,其实就是定义了一个字符类型的数组。其实字符指针做函数参数,就和前面说的数组一样,退化为指针了。

下面看一个求字符串长度的例子:

正常的思路是:定一个一变量,然后遍历字符串,每次自加这个变量即可。但现在我们使用指针求这个问题。思路是:定义两个指针,一个不动,一个自加,最后字符串的长度就是两个指针的差。

代码:

#include <stdio.h>

char* mystr(char* str1, char* str2) {
	return str1;
}

char* my_str_len(char* str) {
	char* dest = str;
	while (*dest) {
		dest++;
	}
	return dest - str;//返回字符串的长度
}

int main() {
	char str1[] = "hello world!";
	char str2[] = "hello c!";
	int len = my_str_len(str1);
	printf("字符串的长度为:%d\n", len);

	return 0;
}

4.3 const修饰的指针变量

const我们都知道,简单的来说就是让变量变成常量。看一下const修饰指针变量,下面的两种情况:

const char* p;
char* const p2;

这两个区别是什么???

答:

  • 前者:说明指针指向的内存不能改变;
  • 后者:说明指针的指向不能改变,指针的值不能修改。

看一个例子:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void)
{
	//const修饰一个变量为只读
	const int a = 10;
	//a = 100; //err

	//指针变量, 指针指向的内存, 2个不同概念
	char buf[] = "aklgjdlsgjlkds";

	//从左往右看,跳过类型,看修饰哪个字符
	//如果是*, 说明指针指向的内存不能改变
	//如果是指针变量,说明指针的指向不能改变,指针的值不能修改
	const char* p = buf;
	// 等价于上面 char const *p1 = buf;
	//p[1] = '2'; //err
	p = "agdlsjaglkdsajgl"; //ok

	char* const p2 = buf;
	p2[1] = '3';
	//p2 = "salkjgldsjaglk"; //err

	//p3为只读,指向不能变,指向的内存也不能变
	const char* const p3 = buf;

	return 0;
}

4.4 指针数组做为main函数的形参

int main(int argc, char *argv[]);
  • main函数是操作系统调用的,第一个参数标明argc数组的成员数量,argv数组的每个成员都是char *类型
  • argv是命令行参数的字符串数组
  • argc代表命令行参数的数量,程序名字本身算一个参数
#include <stdio.h>

//argc: 传参数的个数(包含可执行程序)
//argv:指针数组,指向输入的参数
int main(int argc, char *argv[])
{

	//指针数组,它是数组,每个元素都是指针
	char *a[] = { "aaaaaaa", "bbbbbbbbbb", "ccccccc" };
	int i = 0;

	printf("argc = %d\n", argc);
	for (i = 0; i < argc; i++)
	{
		printf("%s\n", argv[i]);
	}
	return 0;
}

5、总结:

定义说明
int i定义整形变量
int *p定义一个指向int的指针变量
int a[10]定义一个有10个元素的数组,每个元素类型为int
int *p[10]定义一个有10个元素的数组,每个元素类型为int*
int func()定义一个函数,返回值为int型
int *func()定义一个函数,返回值为int *型
int **p定义一个指向int的指针的指针,二级指针
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值