关于变量,我们一般的基础定义为
int x=40;
这里的实际操作是将内存段的某一块以01的方式赋值为40,现在假设这里X的内存段我们称为地址,值得注意的是,地址也是数值,如果我们要在其他变量中存储x的地址,那就必须用到指针这种特殊的变量。
1、取址操作符
什么是取址操作符
对于得到程序中所用到变量地址的方法,可以用取址操作符“&”,返回操作数的内存地址,地址操作符是一元操作数,适应于变量。下面以一个实例演示该操作符是如何使用的。
int main() { int var_int; printf("Insert data "); scanf("d%",&var_int); return 0; }
上面,scanf使用&得到存储用户输入值变量var_int的地址。
得到变量地址
前面我们用int x=40来引出了指针的作用,下面这个例子演示了如何得到内存单元的地址或存储数据变量的地址。
#include<stdio.h> void main() { int var_int = 40; printf("Address of variable "var_int"%p ", &var_int) }
输出为
上例中,我们使用&操作符得到了变量的地址。
如果我们将地址的概念扩展到某个结构变量中,该结构变量本身有包含许多其他变量,那么我们可以借助“取址”操作符得到地址。如下例:
#include<stdio.h> struct node{ int a; int b; }; void main() { struct node p; printf("Address of node = %p ", &p); printf("Address of member variable a = %p ", &(p.a)); printf("Address of member variable b = %p ", &(p.b)); }
输出:
注意到上面输出的第一个成员变量和第二个成员的地址非常接近,这意味着对任意数量的结构体内的成员字段,地址按顺序或依据其大小就近分配。
2、指针声明
通过对取址操作符的学习,我们已经知道如何获取地址了,接下来,我们需要一个变量来存储这个地址,这个特定变量能存储和操作变量地址,称为指针变量。以下为申明指针变量的一般形式:
数据类型 *变量名
实例1:一个指针变量能指向并存储原始数据类型的地址。
int* intptr,char* charptr
这里设计一个名为解引用操作符(*)的特殊操作符,用于帮助编译器识别他是一个指针变量。
实例2:申明结构体
struct inner_node{ int in_a; int in_b; }; struct node{ int *a; int *b; struct inner_node* in_node; };
在上例中, struct inner_node* in_node;为指针变量,其中struct inner_node为数据类型,指针变量名为in_node。
3、指针赋值
指针声明时是没有指向的,程序员必须在解引用他之前让其只想有效的内存地址。
1、利用指针地址&分配变量的地址。
int x = 40; int *ptr; ptr = &x;//使用取址操作符获取变量x的地址。
2、让指针变量指向来自堆的动态分配内存。
int * ptr ptr = (int *)malloc(sizeof(int) * count);
指针变量的大小
对于内存分配来说,指针变量的大小可以是32位,也可以是64位的,这个取决于开发平台。32位平台下,指针变量的大小为(int *、char *、float *和void *)4个字节。
下面的按代码给出了64位平台下不同类型的指针变量占用的内存大小。
#include<stdio.h> #include<conio.h> int main() { int i_var; char c_var; double d_var; char *char_ptr; int *int_ptr; double *double_ptr; char_ptr = &c_var; int_ptr = &i_var; double_ptr = &d_var; printf("Size of char pointer=%d value=%u ",sizeof(char_ptr),char_ptr); printf("Size of integer pointer=%d value=%u ",sizeof(int_ptr),int_ptr); printf("Size of double pointer=%d value=%u ",sizeof(double_ptr),double_ptr); _getch();//接受一个任意键的输入,不用按回车就返回。 return 0; }
结果如下
利用指针变量来验证其变量所占用的内存大小是十分有趣的,下例可以说明这点。
#include <stdio.h> #include <conio.h> struct inner_node { int in_a; int in_b; }; struct node { int *a; int *b; struct inner_node* in_node; }; int main() { struct node *p; int *arrptr; int arr[10]; arrptr = arr; printf("Size of pointer variable (struct node*) = %d ", sizeof(struct node*)); printf("Size of pointer variable pointing to int array =%d ",sizeof(arrptr)); return 0; }
结果为:可以看出,struct类型大小为4个字节,这与内存大小始终为四字节符合。
通过学习,已经知道,指针变量存储地址;使用取值操作符(*)访问该地址存储的值,这种特殊的技术叫做指针的解引用。
有时,为了程序正确运行,我们需要将ptr变量指向一个有效的内存单元。下面的代码说明如何采取适当的方法来实现该目标。(编程时常用的套路,需要非常熟练)
int count = 1;//count 变量用于申请一个整型大小的内存单元 int *ptr = (int *)malloc(sizeof(int)*count);
现将ptr变量指向一个有效的内存单元
*ptr = 10;//给ptr指向的内存单元赋值 free(ptr);//释放ptr指向的内存 *ptr = 20;//此时程序会显示一个分段错误,因为我们试图访问已被释放的内存
若有*ptr=&x,对于表达式 *ptr、*(&x)和x来说,他们是等价的。
4、指针的基本操作
最后我们再学习下指针的基本用法,通过学习了解指针的声明周期、变量范围和栈段等内容。
(1)引用传递
函数之间传递信息的技术有传值法:函数能从调用者接收信息并将结果返回个调用者,但值得注意的是,这项技术值传递给被调用的函数,值传递后被复制到相应的栈中。
另一种方法是通过引用传递来传递变量的内存地址而不是变量值本身。
下面个例子来说明两种操作的不同之处。
void swap(int x, int y) { int temp; temp = x; x = y; y = temp; }
这个函数的目的显然是希望修改调用程序传递的参数,但是这个程序是无效的,应为它是传值操作,它交换的是参数的拷贝,原来的参数值并未参加交换。
void swap(int *x, int *y) { int temp; temp = *x; *x = *y; *y = temp; }
通过对函数使用引用传递进行间接访问操作,可以修改需要修改的变量。
通过对比可以看出,该技术有两个优点:
1、再复制内存地址和复制信息量上,其总是在4字节下复制参数。
2、通过引用可以在不同函数中对某个函数中的局部变量进行操作。
(2)指针与常量
常量指针变量
常量指针是一个仅只想唯一内存地址的指针变量。其声明如下:
<指针类型 *> const <变量名> 如: int * const ptr1,char * const ptr2
以下为常量指着使用规则:
1、常量指针变量声明是必须初始化。
2、一旦完成初始化,常量指针不能再指向其他内存地址。
下例即是一个错误的例子:
int main() { int num1 = 10; int num2 = 20; int* const ptr1 = &num1; ptr1 = &num2;//此处编译会报错,因为它试图将一个常数变量指向一个其他地址 }
常量指针
常量指针是指某个指针变量(即变量的内存地址)不能修改指定内存地址存放的值。常量指针声明:
const<指针类型*><变量名> 如: const int* ptr1,const char* ptr2;
同样,下面是一个错误的使用实例:
int main() { int num1 = 10; const int* ptr1; int *ptr2; ptr1 = &num1; *ptr1 = 20;//无法执行,此处会出错,因为常量指针变量的值无法修改。 num1 = 20;// }
指针常量
指针常量的概念为指针变量是常量。换句话说,指针变量仅只想被初始化的地址内存,指针以后无法指向内存单元。此外,指定指针不能修改制定地址存储的值。即不能改变指针变量的值,也不能修改指定地址存储在该地址的值,即前面提到的两项的综合。
声明; const <指针类型>const <变量名> 如: const int* const ptr1,const char* const ptr2
(3)多级指针
前面我们学过了一级间接引用,但是指针的应用不仅仅如此,指针变量本身的地址也能存储在其他的指针变量中。下面举个例子来引出多级指针。
int a = 10; int *ptr = &a;
显然,ptr中存储的为变量a的地址,这里的ptr我们称之为指针变量指针。
其声明如下:
<数据类型>**<变量名>
星号的数据取决于间接引用的级数。随着星号的数目增加间接引用的级数也会增加。
下面举例说明他们之间的关系
#include<stdio.h> void main() { int num; int *ptr = # int **ptr1 = &ptr; printf("value of var num=%d ", num); printf("value of var num=%d ", *ptr); printf("value of var num=%d ", **ptr1); printf("value of var num=%p ", &num); printf("value of var num=%p ", ptr); printf("value of var num=%p ", *ptr); printf("value of var num=%p ", *ptr); printf("value of var num=%p ", ptr); printf("value of var num=%p ", &ptr); }
结果为
显然
num==*ptr==**ptr1,&num==ptr==*ptr1,&ptr==ptr1.
到这里指针的基本部分就讲解完成了,下篇文章将讲解:
指针运算与一维数组。