C语言--指针1

时间:2023-01-02 22:16:02

0.问题的引入 

  int a = 5;  
a = 1024;// 把数值1024存放到变量a对应的存储单元的地址中去
b = a; //取变量a的值,赋值给b
=>
在C语言中,任何变量都有两层含义:
(1) 代表该变量的地址: 左值 lvalue
(2) 代表该变量的值 : 右值 rvalue
对于变量的访问,只有两种情况:
write: 把一个值写到变量的地址中去
read: 从变量的地址中取变量的值。v
如果我们知道了一个变量的地址,是不是就可以通过该变量的
地址去访问这个变量呢?
可以

如何去通过变量的地址去访问这个变量呢?
=> 指针

1.对象的访问的方式

(1) 直接访问: 通过对象名的访问  

      缺陷:  直接访问,受对象的作用域的限制

    f()
{
static int a = 5;

printf("%d/n", a);
}

main()
{
printf("%d/n", a); //ERROR
受到作用域的限制
}

(2) 间接访问: 通过对象的地址访问,指针访问  

 只要你知道了对象的地址,就可以在任何地方访问它。 它是不受作用域限制。 


2. 什么是指针?  

存储单元(内存)的地址:

     分配给每个变量的内存单元都有一个编号,这个编号就是我们说的存储单元的地址。并且存储单元按字节来编址。

在C语言中,指针的概念与地址差不多,你可以认为指针就是一个地址。

一个变量的地址,我们也可以称为变量的"指针"。  

  & 取地址符 : 单目运算符  "取xxx对象的地址" 

int a;
scanf("%d", &a);

      通过一个对象的指针去访问它,首先要解决 对象的指针 的保存问题。需要定义另外一个变量去保存它的地址,这种变量我们称为指针变量。保存 一个对象的地址的 变量,其实就是一个指针变量 。


3. 指针变量  

 什么是指针变量?  

指针变量也是一个变量,也保存数据,只不过指针变量是用来保存其他对象的地址。

指针变量该如何定义?  

        普通变量的定义方式:  变量的类型  变量名; 

指针变量不是一般的变量, 在定义指针变量的时候,为了区分 , 在指针变量的前面加一个 ‘*’,来表示它是一个指针变量 。

指针变量的定义:  指向的对象的类型 * 指针变量名; 

“指向的对象的类型” :  

  指针变量指向的对象的类型,而不是指针的类型  

      “指向”: 假如一个变量p保存了对象a的地址,那么就说变量p肯定是一个指针变量 ,p 指向 a 

例子:

          int a;
//下面的对象p该如何定义 呢?
// 指向的对象的类型 * p;
// typeof(a) * p;
// =>
int * p;

p = &a; //把a的地址,赋值给p
//p保存了对象a的地址
//p 指向 a
//p -> 指针变量

练习:  

  int a[10];

  能不能定义一个对象p,来保存数组元素a[9]的地址? 

#include<stdio.h>
int main()
{
int *p=NULL;
int a[10];
p=&a[9];
}

4. 与指针相关的运算符 

& : 取地址符  

* : 指向运算符  

 单目运算符   *地址 <=> 地址对应的那个对象 

例子: 
int a = 1024;
int * p ;
p = &a; // p <=> &a *p <=> *&a

*&a :
*对象a的地址
=> 地址对应的那个对象
对象a的地址 对象的那个对象 是不是就是a

so,*p <=> *(&a) <=> a
=> *& 可以直接约掉
char a, b; 

*&a = 'A'; // *&a <=> a,在此处(*&a)代表是变量a的左值

b = *&a; // b = a; (*&a)在此处代表的是变量a的右值

代码分析:

int a = 5;

假如我们要定义一个变量 p ,来保存a的地址,p该如何定义? 

int * p ;

把变量a的地址赋值给p? 

p = &a; 

p也是一个变量,是一个指针变量!

 任何一个变量都有两层含义:  

    p的左值:p本身的地址  

    p的右值: p的值,&a

练习1

#include <stdio.h>
int main()
{
int a = 1024;

//指向对象的类型 * 指针变量名;
//typeof(a) * p;
int * p;
p = &a; //p 指向 a

printf("&a == %p\n", &a);
printf("p == %p\n", p);

*p = 250; // a = 250
printf("a == %d\n", a); //250

int b = *p; // b = a
printf("b == %d\n", a); //250
}

练习2

如下函数该如何设计? 怎么去调用? 

#include <stdio.h>
void func1(int x, int y )
{
int temp;
temp = x;
x = y;
y = temp;
}

void func2(int * x, int * y ) //*x = *&a = main.a , *y = *&b = main.b
{
int temp;
temp = *x; //temp = main.a
*x = *y; //main.a = main.b
*y = temp; //main.b = temp
}


void func3( int * x, int * y )
{
int * t;
*t = *x;
*x = *y;
*y = *t;
}

int main()
{
int a = 5;
int b = 6;
int * p = &a;
int * q = &b;
//func1( a, b ); //调用这个函数的目的,是为了交换变量a 和 变量b 的值
//func2( p , q ); //调用这个函数的目的,是为了交换变量a 和 变量b 的值
func3( &a , &b );
printf("a == %d\n", a); // a == 6
printf("b == %d\n", b); // b == 5
}
//内存的非法访问 => 段错误 : a、数组越界
// : b、指针非法使用

5. 指针变量作为函数参数

传的 还是"值",传递的还是"实参的值"
        "实参的值,可能是某个对象的地址"

C语言中函数参数的传递只能是"传值调用"
形参 = 实参的值
例子: 
void func3( int * x, int * y )
{
int * t; //定义了一个指针变量t,但是t没有被赋值
//t没有被赋值,但是t它有一个值
//只不过这个值是一个undefine

*t => 把t的值当做是一个对象的地址,然后去访问这个对象
*t ,那么对象的存储单元 可读或者可写还是一个不确定的
所以说
假设t指向的对象不可写:
*t = *x ,往t指向的对象的存储空间进行一个写的
操作,这样的操作就可能导致内存的非法访问 => 段错误

假设t指向的对象不可读:
*y = *t ,取t指向的对象的存储空间里面的内容,
这样的操作也有可能导致内存的非法访问 => 段错误
*t = *x;
*x = *y;
*y = *t;
}

野指针:
指向一个未知单元的指针,称之为野指针。
访问野指针,可能会导致内存的非法访问 => 段错误

int *t ; t并没有被赋值,不代表t没有值。
这个值是一个undefine
这样的指针t,称之为野指针。

*t = 1024; //访问野指针,对野指针指向的对象进行写的操作
int b = *t;//访问野指针,对野指针指向的对象进行读的操作

t = &a; //不是访问野指针,给t明确指向,t就不是野指针啦

空指针:
0 NULL
在计算机中,地址为0 的存储空间是不存在
如果一个指针的值,指向空(NULL)的指针,称之为空指针.
访问空指针,一定会导致内存的非法访问 => 段错误
int * p = NULL; //p是一个指向为空的指针,p就是一个空指针

访问空指针: 
*p = 1024;//访问空指针,往一个不存在的地方存放数值1024 => 段错误
int b = *p;//访问空指针,往一个不存在的地方去取一个值,赋值给b => 段错误

p = &b; //不是访问空指针,而是给指针p一个明确指向

练习3

找段错误

#include<stdio.h>
int main()
{
int *t;
int *p=NULL;
int a=1024;
int b=250;
printf("%s--------%d\n",__FUNCTION__,__LINE__);
*t=a;//访问野指针,可能会导致段错误
printf("%s--------%d\n",__FUNCTION__,__LINE__);
*p=b;//访问空指针,一定会导致段错误
printf("%s--------%d\n",__FUNCTION__,__LINE__);

}


6. 数组与指针 

数组元素与普通变量是一样的,数组元素也有自己的地址。
数组元素也有左值和右值,并且数组元素间的地址是相邻的。
数组名可以代表首元素的地址。

int a[10]; 
a => &a[0],when数组名a当做指针来看!!

假如,我们要定义一个指针变量p,来保存数组元素a[0]的地址。
该如何定义P?

typeof(a[0]) * p ;

int * p = NULL;//保存a[0]的地址:
p = &a[0];
//a => &a[0];
p = a;

能不能通过指针p去访问数组元素a[0]:  

*p = 1024; //a[0] = 1024;
b = *p; //b = a[0]

能不能通过指针p去访问数组元素a[1]:
  可以

*p => *&a[0] => a[0]


a[1]的地址和a[0]的地址是相邻的

p + 4 == &a[1] ??? 不对的

指针做加减的问题:  

 p + i(p是一个指针,i是一个整数值) 不是简单的数值上面的加减, 而是加减i个指向单元的长度!!! 

例子:

p + 1 => 往后面挪了一个指向单元的长度
p 指向 a[0]的,指向单元的类型为 typeof(a[0]) => int
p + 1 => 往后面挪了一个int单元的长度
p+1 = &a[1]
*(p+1) = *&a[1] = a[1]

*(p+1) = 250; //a[1] = 250
b = *(p + 1); //b = a[1];

p + i => 往后面挪了i个int单元的长度
p + i => &a[i]
*(p + i) => *&a[i] => a[i]

在C语言中,p一个指针,有:

*(p+i) => p[i], when i >= 0
p + i => &a[i]
*(a + i) => *(&a[0] + i ) => *(&a[i]) => a[i]
*(p + i) <=> *(&a[0] + i) <=> a[i] <=> p[i]

p[1] <=> *(p + 1) <=> *(&a[0] + 1) <=> *(&a[1]) <=> a[1]
a[1] <=> *(a + 1) <=> *(&a[0] + 1) <=> *(&a[1]) <=> a[1]

练习4

  通过指针p去访问数组a中的每一个元素!

  如:  求数组a的元素之和。

#include<stdio.h>
int main()
{
int sum=0;
int a[10]={1,2,3,4,5,6,7,8,9,10};
int * p=&a[0];
int i;
for(i=0;i<10;i++)
{
sum=sum+*(p-2+i);
}
printf("%d\n",*p+1);
}

7. 再论数组名与指针关系

数组名是一个指针常量,是指针就会有类型。 
指针的类型 决定了 指针做加减时 移动的步长。
char c[10];
char * p = c; //p = &c[0];

p + i : 表示指针p往后面挪了i个char单元的长度

int a[10];
int * q = a; //q = &a[0];

q + i : 表示指针q往后面挪了i个int单元的长度

数组名可以看做是指向数组的第0个元素的指针常量。数组名在数值上表示首元素的地址(首地址)。

a <=> &a[0]
typeof(&a[0]) : 
&对象 => xxx的地址 => 指针

typeof(&a[0]) => 指向对象的类型 * => typeof(a[0]) *
=> int *

例子: 

int a;
int * p = &a;
typeof(p) => 指向对象的类型 * => typeof(a) * => int *
typeof(&a) => 指向对象的类型 * => typeof(a) * => int *

例子:
int a[10] = {1,2,3,4,5,6,7,8,9,10};
要定义一个指针p,来保存a[0]的地址!

普通变量定义: 
变量的类型 变量名;
typeof(p) p;
=> typeof(&a[0]) p;
=> int * p;
int * p;

//p = &a[0];
p = a; //此处数组名a,当做一个指针来看的,表示首地址

printf("&a[0] == %p\n", &a[0]);
printf("p == %p\n", p);
printf("a == %p\n", a);//此处数组名a,当做一个指针来看的,表示首地址

printf("%d\n", a[0]);
printf("%d\n", p[0]);
printf("%d\n", *p);
printf("%d\n", *a);
printf("%d\n", *(a + 0));
printf("%d\n", *(p + 0));
......

总结:

1. --------------------------------------------------------------

typeof(&x) => typeof(x) *
&x => 是一个指针
指针的类型如何描述呢?
指向对象的类型 *

&x => 是一个指针,并且保存了x的地址,所以
&x 指向 x
typeof(&x) => 指向对象的类型 *
=> typeof(x) *

2. -----------------------------------------------------------------
&x[y] + i => &x[y+i]

int a[10];
&a[0] 元素a[0]的地址

&a[0] + 1 => &a[1]
&a[0] + i => &a[i]

&a[2] + 3 => &a[5]

3. --------------------------------------------------------------
int a[10];
数组名a,在代码中有两个含义:
a、数组名可以代表整个数组
sizeof(a) => 40
typeof(a) 求整个数组a的类型 => int[10]
&a 取整个数组a的地址

b、数组名在合适的情况下,可以当做指针来看
a => &a[0]
a + 1 => &a[0] + 1 => &a[1]
p = a <=> p = &a[0]
....

练习1. 

int a[10] = {1,2,3,4,5,6,7,8,9,10};

printf("a = %p, a + 1 = %p, &a+1 = %p\n",a, a + 1, &a + 1);
假设&a[0] => 0x4000 0000

a : 当做指针来看 0x4000 0000
&a[0]

a + 1 : 数组名a当做指针来看 0x4000 0004
&a[0] + 1 => &a[1]

&a + 1 : 数组名a当做整个数组来看 0x4000 0028
typeof(&a) => typeof(a) * => int[10] *
&a 是一个指向一个含有10个int元素的数组的 指针
数组指针 : 指向一个数组的指针

&a + 1 => 往后面挪了整个数组的单元长度
=> 往后面挪了10个int单元的长度

练习2.

int b[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
printf("b = %p, b+1 = %p, &b[0]+1 = %p, &b + 1 = %p\n",b, b + 1, &b[0] + 1, &b +1);
2. int b[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
=>
// int[4] b[3] ;
b[0] _ _ _ _
b[1] _ _ _ _
b[2] _ _ _ _
printf("b = %p, b+1 = %p, &b[0]+1 = %p, &b + 1 = %p\n",b, b + 1, &b[0] + 1, &b +1);
假设 &b[0][0] 的数值是为 0x4000 0000
typeof(&b[0][0]) => typeof(b[0][0]) *
=> int *

b : 数组名b, 当做指针来看 0x4000 0000
&b[0] : 此处b[0] 又是一个一维数组名,当做整个一维数组b[0]来看
取整个一维数组b[0]的地址。
typeof(&b[0]) => typeof(b[0]) *
=> int[4] *

b + 1 : 数组名b,当做指针来看 0x4000 0010
&b[0] + 1 : 此处b[0] 又是一个一维数组名,当做整个一维数组b[0]来看
取整个一维数组b[0]的地址 + 1
=> &b[0] + 1 => &b[1] :取整个一维数组b[1]的地址
往后面挪了整个一维数组(int[4])单元长度,
在数值上面是等于 一维数组b[1]的地址

&b[0] + 1 : 数组名b[0]当做整个一维数组b[0]来看 0x4000 0010
=> &b[1] 同上

&b +1 : 数组名b,当做整个二维数组b来看
&b 表示取整个二维数组的地址,在数值上面还是等于&b[0][0] = &b[0]
但是在含义(类型)上面是不一样的。
typeof(&b) => typeof(b) *
=> int[4][3] *

&b + 1 : 往后面挪了12个int单元的长度
: 往后面挪了3个int[4]单元的长度
: 往后面挪了一个int[4][3]单元的长度
  1. 分析如下代码的输出结果?
int a[5] = {1,2,3,4,5};
int * ptr = (int *)(&a + 1);
printf("%d %d\n", *(a + 1), *(ptr - 1)); // 2 5
--------------------------
int a[5] = {1,2,3,4,5};
int * ptr = (int *)&a + 1;

printf("%d %d\n", *(a + 1), *(ptr - 1));// 2 1
  1. 有int b[3][4]; 假如要定义一个指针变量p,来保存b[0][0]的地址, 该如何定义,怎么赋值?
int * p = &b[0][0]; //在数值上是 == &b[0] == &b

typeof(p) <=> typeof(&b[0][0])
=> int *
p = &b[0]; //ERROR
typeof(&b[0]) => typeof(b[0]) *
=> int[4] *

p = &b; //ERROR
typeof(&b) => typeof(b) *
=> int[4][3] *

&b[0][0] <=> b[0](当做指针)
b[0] => &b[0][0]
p = b[0]; //OK

*b => *(&b[0]) => b[0] => &b[0][0]
p = *b ;//OK

8.指针常量 与 常量指针

练习5

#include <stdio.h>
int main()
{

//指针常量
int a = 250;
int * const p = &a;

*p = 1024;
printf("a == %d\n", a); //a == 1024

int b = *p;
printf("b == %d\n", b);//b == 1024

printf("p == %p\n", p);
//p = &b; //ERROR 指针常量的指向不能改变


//常量指针
int a = 250;
int b = 1024;

const int * p = &a;
printf("a == %d\n", *p); //OK a == 250
//*p = 1024; //ERROR error: assignment of read-only location ‘*p’
p = &b; //OK
printf("b == %d\n", *p); // b == 1024
}

指针常量:                                        

 指针本身不能改变(指向不能变),但是指向的空间里面的内容是可以改变的!!!

 如: 数组名  

int a[10];

   把数组名a当做指针来看,那么a就是一个指针常量 ,可以通过数组名a去改变 a指向的空间里面的内容:

    如:  

*(a + i) = 1024; // a[i] = 1024

   但是不能通过改变数组名a的指向:

a <=> &a[0]  
int b;
a = &b; //ERROR
a = a + i ; //ERROR a + i <= >&a[i];

该如何定义一个指针常量?
指向对象类型 * const 指针变量名;
例子:

int a;
int * const p = &a;
*p = 1024;
int b = *p;
p = &b; //ERROR

常量指针:

是指向常量的指针。指针指向的对象是常量,那么这个对象不能改变(指针指向的空间里面的内容是不可变的),但是这个指针的指向是可以改变的(可以指向其他的对象)。
如:

char * p = "123456";

字符串的值 就是 首字符的地址!

typeof("123456") => typeof(&'1') => typeof('1') * => const char *
*(p + 1) = 'B'; // ERROR "1B3456"
char c = *(p + 1); //OK c = '2'
p = "ABCDE"; //OK

常量指针该如何定义?  

  const 指向对象类型 *  指针变量名;  

  or  

  指向对象类型 const *  指针变量名; 

例子: 
int a = 250;
int b = 1024;
const int * p = &a;
printf("a == %d\n", *p); //OK
*p = 1024; //ERROR
p = &b; //OK