【黑马程序员】C语言学习笔记(2) - 指针

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

一、对于指针的理解

    指针是C语言提供的一种数据结构,其功能与一般的数据类型不一样的地方就在于它存储的是变量的内存地址。我对指针的概念理解如下图所示:



对应上图我们可以使用如下的代码来解释:

#include <stdio.h>
int main()
{
    int a = 11;
    int *p = &a;
    int **d = &p;
    printf ("%d %d %d", a, *p, **d);
    return 0;
}

    我们首先申请了一个整形变量a,并将其赋值为11。紧接着我们定义一个一级指针,并将其指向变量a,此操作的实质其实就是在指针变量中存储变量的地址。而指针最强大的功能也恰恰在于像我们提供了间接操作数据的能力。指针的一般申明方式为: 数据类型 * 指针变量名,指针占用4个字节的存储空间。而对于**d则是指针的指针,也即我们常说的二级指针,指向变量我们需要一级指针,指向一级指针则需要一个二级指针,依次类推指向二级指针就需要一个三级指针、、、,对于多级指针的声明差别就在*号数量上。在指针操作中我们经常会使用*和&符号,*号符除了在声明时指明这是一个指针变量外,还有另外一个作用解引用操作,即在打印时执行*p操作其实是告诉编译器去拿到p中存储的地址0x28ff18这个地址中存储的数据变量是多少。而&操作符最主要的作用则是拿到变量的地址。

二、使用指针的注意事项

1、使用指针时记得初始化

       当我们定义一个指针时,最好的方式就是在定义时就对其进行初始化,没有初始化的指针我们称之为野指针。这时候指针里存储着一个随机的未知地址值,如果此时对指针进行操作则很有可能使程序崩溃。比如:

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

int main()
{
	char *p;
	printf("%c\n", *p);
	system("pause");
	return 0;
}
此时会出现如下结果:


所以在定义指针时最好的办法是在定义时就为其初始化一个确定的值,或者实在不行就为其赋值为NULL。

2、不能返回一个临时变量的指针

有时候我们或许会有这样的应用场景,我们在一个自己定义的函数中需要范围一个指针类型,例如以下这段代码:

<pre name="code" class="cpp">#include <stdio.h>
#include <stdlib.h>

int *getNum()
{
	int a = 10 , *b = &a;//定义一个整形变量a=10,定义一个整形指针b指向a
	return b;

}

int main()
{
	printf("%d\n", *(<span style="font-family: Arial, Helvetica, sans-serif;">getNum()</span><span style="font-family: Arial, Helvetica, sans-serif;">));</span>
	system("pause");
	return 0;
 }


 在这里我们就返回了一个临时变量指针b,或许我们在很多时候去访问这个指针的内容发现结果是对的,但是其实这是一种很危险的使用方式。C语言中函数中的临时变量都是在调用函数时在栈帧上进行内存分配的临时变量,当函数运行结束时所有的临时变量都会被销毁。所以对于b,其返回的内存地址其实在函数结束时就已经释放了,如果此时有别的变量正好占用了这块内存那么结果就会出错。 
3、使用指针作为形参时会对原始数据造成影响 

我们可能会看到这样一段代码:

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

void swap1(int a, int b)
{
	int temp = a;
	a = b;
	b = temp;
}

void swap2(int *a, int *b)
{
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;
}

int main()
{
	int a =4, b =5;
	swap1(a, b);
	printf("%d %d\n", a, b);
	swap2(&a, &b);
	printf("%d %d\n", a, b);
	system("pause");
	return 0;
}
上述代码的运行结果为:4,5; 5,4,第一个函数并没有如愿将a,b的值进行交换,而第二个函数则实现了值的交换。两个函数的主要区别就是一个使用值传递,一个使用指针传递。使用值传递的形参只是对实参的一份拷贝并不会改变原始的值,而使用指针则是传递了实参的地址,所以对形参的操作就相当于对实参进行操作。

三、数组与指针

1、指针与数组的关系
在C语言中当我们想存储一连串的值时,我们往往会使用数组进行存储,比如我们使用一个字符数组存储字符串“Hello World”,char ch[] = "Hello World"。那么在内存中是怎么存储这个字符串的呢。答案是在内存中会使用连续的地址存储每一个字符,从H一直存储到d,并且在d之后会存储一个结束符'\0'。当我们学习数组时就知道数组的下标从0开始,这是因为下标表示的其实是当前数据相对于起始位置的偏移量,所以第一个字符‘H’的存储地址其实就是首地址,所以偏移量就为0。

       对于数组来说其数组名就相当于首地址即数组名ch代表的地址与&ch[0]的值相等,因此我们可以理解数组名就像是一个数组,但是数组名与指针并不是完全是一回事。比如:char ch[] = "Hello World",char *p = ch;虽然p中存储的地址与ch表示的地址是相等的,但是当我们对ch和p都使用sizeof()运算符的时候,sizeof (ch)得到的结果是 12,即当前数组占用的字节数。但sizeof(p)得到的结果是4,这是32为机器下指针占用的字节数。此外我们需要明白的一个事实是,p是一个指针变量,我们可以修改p指向另外一个地址但是我们不能修改数组名ch指向的地址。

2、指针数组与数组指针

指针数组、数组指针听起来有些拗口,但是名字顺序的不一样代表的类型也不一样。

    指针数组,顾名思义就是数组的元素全部为指针的数组。这句话描述的是数组中每一项存储的数据的数据类型,定义一个指针数组的格式为:数据类型 * 指针数组名[数组长度]。例如:

char ch[2][3] = {{'H', 'e', 'l'}, {'l', 'l', 'o'}}; char *cp[2] = {ch[0], ch[1]};这就定义了一个字符指针数组,且字符指针数组总共存储两个字符指针分别为{'H', 'e', 'l'}和{'l', 'l', 'o'}。

数组指针,数组指针就是说明首先这是一个指针,其次这个指针是指向数组的,其重点是变量本身是一个指针。定义一个数组指针的语法是:数据类型 (*指针名称)[指向的数组的数组长度],如:int[2][2] = {1 ,2, 3, 4}; int (*p)[2] = a;这样则定义了一个数组指针,这个指针指向一个二维数组,其中p[0]指向第一行,p[1]指向第二行。

3、函数指针

在我的理解中什么是函数指针?函数指针与别的类型的指针没有任何不一样,他还是一个指针类型,只不过它特殊的地方在于他指向的是一个函数的地址。C语言中函数也是以二进制数据的形式存储在内存中,当我们调用一个函数时,实质我们也是找到函数的存储地址读出函数然后调用他。所以我们也可以使用指针来找到函数调用,在函数中我们关注的是函数的返回类型、形参个数与类型,所以我们在定义一个函数指针时需要描述清楚这些。定义一个函数指针的语法为:返回值类型 (*函数指针名称)(参数类型, 参数类型),例如我们定义一个print(char a[ ])函数并使用函数指针void (* fun)(char a[])来调用它:

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

void print(char a[])
{
	printf("%s\n", a);
}

int main()
{
	char ch[] = "I love program!";
	void (* fun)(char a[]) = print;
	fun(ch);
	system("pause");
	return 0;
}
运行结果:
<img src="https://img-blog.csdn.net/20151112220416108" alt="" />





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值