博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C语言提高之指针初步
阅读量:4124 次
发布时间:2019-05-25

本文共 5480 字,大约阅读时间需要 18 分钟。

1、指针也是一种数据类型

        指针是一种数据类型,是指它指向的内存空间的数据类型;

        *号表示 操作 指针所指向的内存空间中的值;

        *p相当于通过地址(p变量的值)找到一块内存;然后操作内存;

        *p放在等号的左边赋值(给内存赋值);
        *p放在等号的右边取值(从内存获取值);

        不断的给指针变量赋值,就是不断的改变指针变量(和所指向内存空间没有任何关系);

        指针做函数参数 形参有多级指针的时候, 站在编译器的角度 ,只需要分配4个字节的内存(32bit平台),当我们使用内存的时候,我们才关心 指针所指向的内存 是一维的还是二维的。

2、指针指向某个变量,就是把某个变量地址赋给指针;

      指针变量  和  它指向的内存块变量  是两个不同的概念;释放了指针所指的内存空间,但是如果指针变量没有重置成NULL,则会出现“野指针”的情况。

                 避免方法:(1)定义指针的时候,初始化成NULL;(2)释放指针所指向的内存空间以后,紧接着把指针重置成NULL。

3、一级指针的典型用法:数组 int buf[10]、字符串 

1).C语言的字符串 以零结尾的字符串

2).在C语言中没有字符串类型, 通过 字符数组 来模拟字符串
3).字符串的内存分配,堆上、栈上、全局区 (很重要)
4).注意:buf是一个指针,是一个只读的常量,也就是说buf是一个常量指针,不能别修改指向(也就是地址)。——这是必须的,也是显而易见的,为了保护数组的首地址,析构内存的时候,保证buf所指向的内存空间安全释放

p = buf; //buf 数组首元素的地址	for (i=0; i
5).字符串作函数参数
//不要轻易改变形参的值, 要引入一个辅助的指针变量. 把形参给接过来int copy_str26_good(char *from , char *to){	//*(0) = 'a';	char *tmpfrom = from;	char *tmpto = to;	if ( from == NULL || to == NULL)	{		return -1;	}	while ( *tmpto++ = *tmpfrom++ ) ; //tmpfrom在不断变化!    //空语句	printf("from:%s \n", from);  //打印成功!from未变化!		}int main(){	int ret = 0;	char *from = "abcd";	char buf2[100]; 	copy_str26_good(from, buf2);	printf("copy_str25_err end\n");	return 0;}

       应用场景:char *p = "abcdbcd123123cdacbdabcdabcdabcdaabcd"; ,求字符串p中 abcd出现的次数,1).请自定义函数接口,完成上述需求 ,2).自定义的业务函数 和 main函数分开。

int count(char *source, char *sub){	int count = 0;	char *tmp_source = source;//不轻易改变形参的值	char *tmp_sub = sub;	for (; *tmp_source != '\0'; tmp_source++)//循环检测原字符串	{		char *temp = tmp_source;//记录原字符串匹配位置,以便复位		while (*tmp_source != '\0')//开始一趟匹配		{			if (*tmp_source != *tmp_sub)			{				break;			}			tmp_source++;			tmp_sub++;			if (*tmp_sub == '\0')				count++;		}		tmp_sub = sub;//复位		tmp_source = temp;//复位	}	return count;}void main(){	char *p = "abcd111122abcd3333322abcd3333322qqq";	char buf[] = "abcd";	int sub_count = count(p, sub_str);	printf("sub_str(abcd) count: %d\n", sub_count);}

void get_count(char *source, char *sub, int *count){	if (source == NULL || sub == NULL || count == NULL)//增强程序的健壮性	{		printf("func getCount() err (source==NULL || sub==NULL || count==NULL) \n");		return -1;	}	char *tmp_sub = sub;	for (; *source != '\0'; source++)//循环检测原字符串	{		char *temp = source;//记录原字符串匹配位置,以便复位		while (*source != '\0')//开始一趟匹配		{			if (*source != *tmp_sub)			{				break;			}			source++;   			tmp_sub++;			if (*tmp_sub == '\0')				(*count)++;//注意:不是*count++!++的优先级高于*。否则将会是地址移动!这里应该是对*count		}		tmp_sub = sub;//复位		source = temp;//复位      		//这里有的程序优化为:当查找成功时,复位到+strlen(sub)。我觉得不合适!试想一下在“aaaa”中查找“aa”!	}	printf("sub_str(abcd) count: %d\n", *count);}void main(){	char *p = "abcd111122abcd3333322abcd3333322qqq";	char buf[] = "abcd";	int num = 0;	get_count(p, buf, &num);}

4、二级指针:指向指针变量的指针,存放地址值的地址。一级指针所关联的是其值(一个地址)名下空间里的数据,这个数据可以是任意类型并做任意用途,但二级指针所关联的数据只有一个类型一个用途,就是地址。一级指针的值虽然是地址,但这个地址做为一个值也需要空间来存放,是空间就具有地址 ,这就是存放地址这一值的空间所具有的地址,二级指针就是为了获取这个地址。

     例如:如果A、B、C都是变量,即C是普通变量,B是一级指针变量,其中存放着C的地址,A是二级指针变量,其中存放着B的地址,则这3个变量分别在内存中占据各自的存储单元,它们之间的相互关系下图所示,相互之间的前后位置关系并不重要.此时,B是一级指针变量,B的值(即C的地址)是一级指针数据;A是二级指针变量,A的值(即B的地址)是二级指针数据.

       关于为什么要使用二级指针?参见:http://blog.csdn.net/hmsiwtv/article/details/7413168     \      http://blog.csdn.net/anna39/article/details/6769177    \      http://blog.csdn.net/mhjcumt/article/details/7351032

对上述参见资料的一些分析:

谨记:指针存放的是某变量的地址,但是同时指针变量又有存放自己的地址。

程序是想修改p的地址,所以要把p的地址作为实参扔给形参,所以形参应该是二级指针。

如果形参是一级指针,则在被调用函数中对该指针(形参)的地址修改一万次,对主调函数的实参指针都没有任何作用!

对于多级指针,只需从右向左一级一级分析即可。*号就像一把钥匙,通过后边的地址去操作存放在地址中的内容。

间接赋值:(形参指针级别需要高)

a.用 1级 指针作形参,去间接修改了 0级 指针(实参)的值;
b.用 2级 指针作形参,去间接修改了 1级 指针(实参)的值;
c.用 3级 指针作形参,去间接修改了 2级 指针(实参)的值;
d.用 n级 指针作形参,去间接修改了 n-1级 指针(实参)的值;

对于参见资料2,用函数返回值来传递动态内存,不要用return语句返回指向“栈内存”的指针,要返回一个“常量”指针或者一个“堆内存指针”。(详见上篇内存四区专题)

对于参见资料1,当使用一级指针的被调函数中不断修改pa的指向,在此时形参结合所发生的事:array得到了数组名为str, search得到了a的值, pa得到了p的值(而非p自身的地址)!但实参p并未得到形参pa传回的值(某元素的地址)。可见尽管使用了指针,也并没实现传址,当实参形参都是指针时,它们也仅仅是传值——传了“别人的”地址,没有传回来。正如我们以前所知的,想要在被调函数中修改主调函数中的某值,可以使用(一级)指针做函数参数。

       但是在参见资料1中,我们的需求是在被调函数中修改主调函数中的某值的地址值,所以要使用二级指针——指向指针的指针。这样,我们在传递变量时find2(str, a, &p); 而非find1(str, a, p);,传递的是p的地址,而不是传递指针变量p所保存的地址(这不是p的地址)。 

void getMem(char **p2){	*p2 = 400; //间接赋值  p2是p1的地址}void getMem2(char *p2){        //char *p2;放在形参位置和放在函数这儿一样,	p2 = 800; // 所以在此修改p2对主调函数中的p1没有任何影响}void main(){	char *p1 = NULL;	char **p2 = NULL;	p1 = 0x11;	p2 = 0x22;	//直接修改p1的值	p1 = 0x111;	//间接修改p1的值	p2 = &p1; 	*p2 = 100; //间接赋值  p2是p1的地址	printf("p1:%d \n", p1);	//{	//	*p2 = 200; //间接赋值  p2是p1的地址	//	printf("p1:%d \n", p1);	//}	getMem(&p1);//传递的是p1的地址	getMem2(p1);//传递的是p1保存的地址	printf("p1:%d \n", p1);	system("pause");	return ;}
应用场景:
int  getMem3(char **myp1, int *mylen1,  char **myp2, int *mylen2){	int	ret = 0;	char	*tmp1, *tmp2;	tmp1 = (char *)malloc(100);//原来p1指向NULL,现在要让p1指向新分配的堆内存	strcpy(tmp1, "1132233");	//间接赋值 	*mylen1 = strlen(tmp1);  //1级指针的间接赋值	*myp1 = tmp1; //2级指针的间接赋值	tmp2 = (char *)malloc(200);//原来p2指向NULL,现在要让p2指向新分配的堆内存	strcpy(tmp2, "aaaaavbdddddddd");        //间接赋值	*mylen2 = strlen(tmp2);  //1级指针的间接赋值	*myp2 = tmp2; //2级指针的间接赋值	return ret;}int  main(){	int	ret = 0;	char	*p1 = NULL;	int	len1 = 0;	char	*p2 = NULL;	int	len2 = 0; 	ret = getMem3(&p1, &len1, &p2, &len2);//测试是否调用成功	if (ret != 0)	{		printf("func getMem3() err:%d \n", ret);//如果不成功返回错误码		return ret;	}	printf("p1:%s \n", p1);	printf("p2:%s \n", p2);		if (p1 != NULL)//释放内存	{		free(p1);		p1 = NULL;	}	if (p2 != NULL)	{		free(p2);		p2 = NULL;	}	system("pause");	return ret;}
函数调用时,形参传给实参,用实参取地址,传给形参,在被调用函数里面用*p,来改变实参,把运算结果传出来。——C语言 的精华。

间接赋值成立的三个条件:
条件1  //定义1个变量(实参) //定义1个变量(形参)
条件2  //建立关联:把实参取地址传给形参,实参可能是普遍变量,也有可能是指针变量。
条件3  //*形参去间接地的修改了实参的值。
//1 2 3 这3个条件 写在有一个函数
//12 写在一块,3 单独写在另外一个函数里面  ==>函数调用
//1      23写在一块 ==> C++会有

你可能感兴趣的文章
JDK命令行工具的监控
查看>>
http协议知识点快速总结
查看>>
org.springframework.web.util.NestedServletException: Request processing fail
查看>>
SpringMVC处理异常的三种方式
查看>>
IDEA中如何使用debug调试项目 超详细教程
查看>>
SpringIOC和AOP原理
查看>>
Java的Object.wait(long)在等待时间过去后会继续往后执行吗
查看>>
ConcurrentHashMap面试问题
查看>>
从输入url到浏览器加载过程
查看>>
校招面试时如何做项目介绍
查看>>
分布式和微服务理解
查看>>
面试时会问到的项目中的问题总汇
查看>>
Netty有什么用?
查看>>
HashMap为什么2倍扩容
查看>>
详解 MySql InnoDB 中的三种行锁(记录锁、间隙锁与临键锁)
查看>>
解决docker 启动 centos 镜像,容器会自动退出问题
查看>>
ftp文件出现乱码
查看>>
ES安装启动及could not find java in bundled jdk at /opt/elasticsearch/elasticsearch-7.9.1/jdk/bin/java报错
查看>>
什么是脑裂?Zookeeper怎么解决脑裂问题的?
查看>>
redis加锁的几种方法
查看>>