——指针)

Posted 二木成林

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了——指针)相关的知识,希望对你有一定的参考价值。

10.1 地址指针的基本概念

10.1.1 数据类型的字节数

首先在计算机中,所有的数据都是存放在存储器中的。一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等。如整型量占2个单元,字符量占用1个单元等。

可以用下面的代码来查看各种数据类型占用的字节,即占用的内存单元(不同的电脑和编译环境可能有所不同):

#include <stdio.h>

void main() 
	printf("Size of int is:%d\\n", sizeof(int));
	printf("Size of unsigned int is:%d\\n", sizeof(unsigned int));
	printf("Size of short is:%d\\n", sizeof(short));
	printf("Size of unsigned short is:%d\\n", sizeof(unsigned short));
	printf("Size of long is:%d\\n", sizeof(long));
	printf("Size of unsigned long is:%d\\n", sizeof(unsigned long));
	printf("Size of long long is:%d\\n", sizeof(long long));
	printf("Size of unsigned long long is:%d\\n", sizeof(unsigned long long));
	printf("Size of char is:%d\\n", sizeof(char));
	printf("Size of signed char is:%d\\n", sizeof(signed char));
	printf("Size of unsigned char is:%d\\n", sizeof(unsigned char));
	printf("Size of float is:%d\\n", sizeof(float));
	printf("Size of double is:%d\\n", sizeof(double));
	printf("Size of long double is:%d\\n", sizeof(long double));


在上面知道了内存单元的概念后,为了正确地访问这些内存单元,必须为每个内存单元编上号。根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。

根据下面的代码,但现在先不必关心代码的具体内容,可以对地址有个更直观的感受:

#include <stdio.h>

void main()
	int a=100;		// 4字节 
	int b=5; 	    // 4字节 
	int c=1;   	    // 4字节 
	double d=14.0;	// 8字节 
	double e=1.23;  // 8字节
	double f=12.5;  // 8字节 
	 
	printf("| 变量名 |     值      |       地址       |\\n");
	printf("|--------|-------------|------------------|\\n");
	printf("|    a   |     %d     |    %d       |\\n", a, &a);
	printf("|--------|-------------|------------------|\\n");
	printf("|    b   |     %d       |    %d       |\\n", b, &b);
	printf("|--------|-------------|------------------|\\n");
	printf("|    c   |     %d       |    %d       |\\n", c, &c);
	printf("|--------|-------------|------------------|\\n");
	printf("|    d   |     %1.1f    |    %d       |\\n", d, &d);
	printf("|--------|-------------|------------------|\\n");
	printf("|    e   |     %1.2f    |    %d       |\\n", e, &e);
	printf("|--------|-------------|------------------|\\n");
	printf("|    f   |     %1.1f    |    %d       |\\n", f, &f);
	printf("|--------|-------------|------------------|\\n");


代码解释:

  • 其中a变量是一个int变量,从上面代码中知道占4个字节,这里看不出来,但是其他的数据类型看得出来。
  • 其中b变量是一个int变量,地址是6487580,距离a变量的地址为4字节。
  • c变量也是int变量,地址是6487572,距离b变量的地址也是4字节。
  • d变量是double型变量,距离c变量的地址是12字节,并不是double数据类型所占据的8字节。(至于为什么是12而不是8,暂时不清楚,不过可以不用关注这个点,C语言也没有规定说明变量必须是连续存储的。)
  • e变量也是double型变量,距离d变量的地址是8字节,相同数据类型的变量看起来是连续存储的。
  • f变量同样如此。

需要注意的是,内存单元的指针和内存单元的内容是不同的概念。

对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容。在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。

10.1.2 关于直接访问和间接访问

10.1.2.1 直接访问

a=5;。系统在编译时,已经对变量分配了地址,例如,若变量a分配的地址是2000,则该语句的作用就是把常数5保存到地址为2000的单元。

#include <stdio.h>

void main()
	// 这是一个名为a的整型变量
	// 该变量的值是5
	// 而为变量a分配的地址是6487580
	// 该语句的作用就是把常数5保存到地址为6487580的单元 
	int a=5;
	// 这是一个名为b的整型变量
	// 该变量的值是6
	// 而为变量b分配的地址是6487576
	// 该语句的作用就是把常数6保存到地址为6487576的单元 
	int b=6;
	// 这是一个名为c的整型变量
	// 该变量的值是7
	// 而为变量c分配的地址是6487572
	// 该语句的作用就是把常数7保存到地址为6487572的单元
	int c=7;
	// 通过&a、&b、&c来输出变量a、b、c的地址 
	printf("&a=%d, &b=%d, &c=%d",&a,&b,&c);
 

结果:

10.1.2.2 间接访问

scanf("%d", &a);。调用函数时,把变量a的地址传弟给函数scanf,函数首先把该地址保存到一个单元中,然后把从键盘接收的数据通过所存储的地址保存到a变量中。

#include <stdio.h>

void main()
	int a;
	scanf("%d", &a);
	printf("您输入一个整数:%d", a);
 

10.1.3 指针

在C语言中,指针是一个特殊的变量,存储地址。

假设我们定义了一个指针变量int* i_pointer用来存放整型变量i的地址。可以通过语句i_pointer=&i;。这里的*符号是用来标志该变量是一个指针变量的。

将i的地址(2000)存放到i_pinter中。这时,i_pointer的值就是(2000),即变量i所占用单元的起始地址。

要存取变量i的值,可以采用间接方式:先找到存放"i的地址"的变量i_pointer,从中取出i的地址(2000),然后就可以取出i的值了。

图形表示:

#include <stdio.h>

void main()
	// 这是一个名为i,值为3的普通变量,可以通过&i打印出该变量的地址 
	int i=3;
	printf("变量i的地址为:%d\\n", &i);
	
	// 这里使用*符号声明了一个指针变量i_pointer 
	int* i_pointer; 
	// &i的意思是取变量i的地址,那么这句就是将i变量的地址赋值给指针变量i_pointer 
	i_pointer=&i;
	// 那么i_pointer的值就是变量i的地址,下面打印的也是i的地址 
	printf("变量i_pointer的值为:%d\\n", i_pointer);
	// 通过&i_pointer可以打印指针变量i_pointer的地址 
	printf("变量i_pointer的地址为:%d", &i_pointer); 
 

10.1.4 认识操作符*&

  • *:取值操作符
  • &:取址操作符
#include <stdio.h>

void main()
	// 声明一个普通变量 
	int i=2000;
	// 声明一个指针变量,这里的*不是取值操作符使用 
	int *pointer;
	pointer=&i;// 将变量i的地址赋给指针变量pointer作为值
	printf("指针变量pointer所指向变量的值为:%d\\n", *pointer); 
	
	// 其他输出,通过这些输出更能深刻体会指针变量 
	printf("普通变量i的值为:%d\\n", i);
	printf("普通变量i的地址为:%d\\n", &i);
	// printf("普通变量i的值为:%d\\n", *i);// 该句代码运行会报错,因为变量i只是一个普通变量,不能使用取值操作符*。 
	printf("指针变量pointer的值为:%d\\n", pointer);// 由于pointer是一个指针变量,所以使用普通变量的打印值方式输出的就是变量i的地址
	printf("指针变量pointer的地址为:%d\\n", &pointer);// 无论是普通变量还是指针变量都可以通过&符号取该变量的地址 

代码解释:

  • 如果要声明一个指针变量,必须要使用*标识符,例如int* pointer。而int pointer就表示的是一个普通变量。
  • &是一个取址操作符,取的就是变量的地址,无论是普通变量还是指针变量都可以取地址,例如上面代码中的&i&pointer
  • 如果要输出变量的值,都可以直接使用变量名即可,无论是指针变量还是普通变量,例如上面代码中的ipointer
  • 但如果要输出指针变量所指向变量的值,那么就需要用到了取值操作符*了,例如上面代码中的*pointer
  • 注意,普通变量不能使用*取值操作符,例如上面代码中的*i,那么运行编译会报错。
#include <stdio.h>

int main() 
    int num = 123;// 普通变量
    int *p = &num;// 指针变量

    printf("%d\\n", num);// 123 普通变量的值
    printf("%d\\n\\n", p);// 6487580 指针变量的值,指针变量存储的是普通变量num的内存地址,注意不同编译器打印出来的地址不一样

    printf("%d\\n", &num);// 6487580 普通变量的地址,等于指针变量p的值
    printf("%d\\n\\n", &p);// 6487568 指针变量的地址

    // printf("%d\\n", *num);// 编译报错,即无法通过*取值运算符取出普通变量的值
    printf("%d\\n", *p);// 123 指针变量所指向变量的值,即普通变量num的值,可以通过*取值运算符取出指针变量所指向变量的值

10.2 变量的指针和指向变量的指针变量

指针与指针变量是有区别的,如果知道了一个变量的地址(内存中的地址),那么就可以通过这个地址来访问这个变量了,因此,就把变量的地址称为该变量的“指针”。

C语言中可以声明一类特殊的变量,这类变量专门用来存放普通变量的地址,称为指针变量。

注意:指针变量的值(即指针变量中存放的值)是普通变量的地址(即指针)。

严格地说,一个指针是一个地址,是一个常量。而一个指针变量却可以被赋予不同的指针值,是变量。但常把指针变量简称为指针。为了避免混淆,我们中约定:“指针”是指地址,是常量,“指针变量”是指取值为地址的变量。定义指针的目的是为了通过指针去访问内存单元。

10.2.1 定义一个指针变量

对指针变量的定义包括三个内容:

  • 指针类型说明,即定义变量为一个指针变量;
  • 指针变量名;
  • 变量值(指针)所指向的变量的数据类型。

基本语法是:

// 定义指针变量之后赋值
类型说明符* 变量名;
变量名=&普通变量;

// 定义指针变量的同时并赋值
类型说明符* 变量名=&普通变量;

其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。下面都是合法的指针变量定义,例如:

// pointer是指向float型变量的指针变量
float* pointer;

// c是指向字符型变量的指针变量
char* c;

可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向一个该变量。

char a='A';
// 定义指针变量
char* c;
// 给指针变量赋值
c=&a;

!

10.2.2 在定义指针变量时需要注意的情况

10.2.2.1 指针变量前面的*表示该变量的类型为指针型变量

定义指针变量的基本语法:

类型说明符* 变量名;
// 还可以写成以下形式,星号在任意靠近谁都可以
类型说明符 * 变量名;
类型说明符 *变量名;

其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。例如:float *pointer_1。注意,指针变量名是pointer_1,而不是*pointer_1

10.2.2.2 在定义指针变量时必须指定基类型

需要特别注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中。所以下面的赋值是错误的:

float a;
int *pointer_1;
pointer_1=&a;
/* 将float型变量的地址放到指向整型变量的指针变量中,是错误的 */

10.2.3 指针变量的引用

指针变量使用前不仅需要定义说明,而且必须赋予具体的值,未经赋值的指针变量(如int* a;中的a)不能使用,否则将造成系统混乱,甚至死机。

指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误。如int* a=123;是错误的。

在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址,所以需要通过某些方式来获取变量的具体地址。

注意:指针变量中只能存放地址(指针),不要将一个整数(或任何其他非地址类型的数据)赋给一个指针变量,否则编译器也会把该值当成一个地址来处理。

因此C语言中提供了地址运算符&取变量的地址。基本语法如下:

&变量名;

例如&a表示变量a的地址,&b表示变量b的地址,当然,变量本身是需要预先声明的。

指针变量初始化的语法如下:

// 定义指针变量之后赋值
类型说明符* 变量名;
变量名=&普通变量;

// 定义指针变量的同时并赋值
类型说明符* 变量名=&普通变量;

例如:

int a=123;

// 定义指针变量之后再赋值
int* p;
p=&a;

// 定义指针变量的同时并赋值
int* p=&a;

// 注意,不允许将一个数值直接赋予给指针变量,因此下面的赋值是错误的
int* p=1000;

实例:

#include <stdio.h>

int main() 
    /* 定义普通变量和指针变量 */
    int a, b;// 普通变量a和b
    int* pointer_1;
    int* pointer_2;// 指针变量pointer_1和pointer_2

    /* 为变量赋值 */
    a = 100;
    b = 10;
    pointer_1 = &a;
    pointer_2 = &b;

    /* 打印变量的值 */
    printf("%d, %d\\n", a, b);// 100, 10
    printf("%d, %d\\n", *pointer_1, *pointer_2);// 100, 10

10.2.4 对&*运算符的说明

如果已经执行了语句pointer_1=&a;

10.2.4.1 &*pointer_1的含义是什么?

#include <stdio.h>

int main() 
    // 定义变量并且赋值
    int a = 5;
    int* pointer_1 = &a;

    // 打印 &*pointer_1
    printf("&a=%d\\n", &a);// &a=6487572
    printf("&*pointer_1=%d", &*pointer_1);// &*pointer_1=6487572

会发现&a&*pointer_1的结果一样,都是普通整型变量a的地址。为什么呢?

&*两个运算符的优先级别相同,但按自右而左方向结合,因此先进行*pointer_1的运算,它运算结果是变量a,再执行&运算,就是对变量a取址。即

&*pointer_1
    =&(*pointer_1)
    =&(a)
    =&a

因此&\\*pointer_1&a相同,即变量a的地址。

如果有pointer_2=&*pointer_1;,则它的作用是将&a(a的地址)赋给pointer_2,如果pointer_2原来指向b,经过重新赋值后它已不再指向b了,而指向了a

#include <stdio.h>

int main() 
    int a = 5, b = 10;
    int* pointer_1 = &a;
    int* pointer_2 = &b;

    printf("&a=%d\\n", &a);// &a=6487564 打印普通变量a的地址
    printf("&*pointer_1=%d\\n", &*pointer_1);// &*pointer_1=6487564 其中&*pointer_1=&(*pointer_1)=&(a)=&a,即普通变量a的地址
    printf("&b=%d\\n", &b);// &b=6487560 打印普通变量b的地址
    printf("&*pointer_2=%d\\n", &*pointer_2);// &*pointer_2=6487560 其中&*pointer_2=&(*pointer_2)=&(b)=&b,即普通变量b的地址

    // 重新赋值
    pointer_2 = &*pointer_1;// 其中&*pointer_1=&(*pointer_1)=&(a)=&a,所以pointer_2 = &*pointer_1 = &a

    printf("&*pointer_2=%d\\n", &*pointer_2);// &*pointer_2=6487564 所以最后打印的是变量a的地址

10.2.4.2 *&a的含义是什么?

&*两个运算符的优先级别相同,但按自右而左方向结合,因此先进行&a运算,得到变量a的地址,再进行*运算,即&a所指向的变量,也就是变量a。即过程如下:

*&a
    =*(&a)
    =*(变量a的地址)
    =a

因此*&a*pointer_1的作用是一样的,它们都等价于变量a

*&aa*pointer_1等价。

#include <stdio.h>

int main() 
    int a = 5;
    int* pointer_1 = &a;

    printf("a=%d\\n", a);// a=5
    printf("*pointer_1=%d\\n", *pointer_1);// *pointer_1=5
    printf("*&a=%d\\n", *&a);// *&a=5

10.2.4.3 (*pointer_1)++相当于a++

注意,括号是必要的,如果没有了括号,就成为了*pointer_1++。其中++*为同一优先级别,而结合方向是自右向左的,因此它就相当于*(pointer_1++)

由于++pointer_1的右侧,是“后加”,因此先对pointer_1的原值进行*运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。

#include <stdio.h>

int main() 
    int a = 5;
    int* pointer_1 = &a;

    printf("a=%d\\n", a);// a=5
    printf("*pointer_1=%d\\n", *pointer_1);// *pointer_1=5
    printf("a++=%d\\n", a++);// a++=5 本语句执行完成后a的值已经变成了6
    printf("(*pointer_1)++=%d\\n", (*pointer_1)++);// (*pointer_1)++=6 所以这里输出的值为6
    printf("a=%d\\n", a);// a=7 又进行一个a++运算,所以最终a的值为7

10.2.4.4 p2=p1;*p2=*p1;是什么含义(其中p1p2都是指针变量)

赋值表达式p2=p1;

使得p2p1指向同一对象i,此时*p2就等价于i,而不是j,如图所示:

代码演示:

#include <stdio.h>

int main() 
    char i = 'a', j = 'b';
    char* p1 = &i;
    char* p2 = &j;

    printf("*p1=%c, p1=%d\\n", *p1, p1);// *p1=a, p1=6487567 打印普通变量i的值和地址
    printf("*p2=%c, p2=%d\\n", *p2, p2);// *p2=b, p2=6487566 打印普通变量j的值和地址

    // 赋值,值和地址都会改变
    p2 = p1;// p1=&i,所以p2=p1=&i
    printf("\\np2=p1;\\n\\n");

    printf("*p1=%c, p1=%d\\n", *p1, p1);// *p1=a, p1=6487567 打印普通变量i的值和地址
    printf("*p2=%c, p2=%d\\n", *p2, p2);// *p2=a, p2=6487567 打印普通变量i的值和地址

赋值表达式*p2=*p1;

则表示把p1指向的内容赋给p2所指的区域,此时就变成如图所示:

代码演示:

#include <stdio.h>

int main() 
    char i = 'a', j = 'b';
    char* p1 = &i;
    char* p2 = &j;

    printf("*p1=%c, p1=%d\\n", *p1, p1);// *p1=a, p1=6487567 打印普通变量a的值和地址
    printf("*p2=%c, p2=%d\\n", *p2, p2);// *p2=b, p2=6487566 打印普通变量b的值和地址

    // 赋值,值改变,地址没有变
    *p2 = *p1;// 将p2指针变量的值修改为普通变量i的值,但实际上没有修改指针变量p2的地址
    printf("\\n*p2=*p1;\\n\\n");

    printf("*p1=%c, p1=%d\\n", *p1, p1);// *p1=a, p1=6487567 
    printf("*p2=%c, p2=%d\\n", *p2vector中储存指针要不要赋初值

C语言中用const声明全局变量赋初值和不赋初值有何区别?变量存放位置有啥不一样?

AVR单片机的堆栈指针SP初值是啥

C语言中关于结构体指针为啥不能在函数内赋初值的问题?

结构数据类型实验

指针常量常量指针和指向常量的常量指针