【笔记】【C语言】第八章 善于利用指针
8.1 地址和指针
(1)数据在内存中是如何存储的?什么是地址?
首先,我们会定义一个变量,用于存储一个数据。比如 int a=1;
然后在编译时,系统遇到定义语句,就给a分配内存单元。因为是整数类型的,所以分配4个字节。
内存区的每个字节,系统都做了编号,这就是“地址”。假设分给a的内存单元是2000-2003。所以我们在使用变量a的时候,会从2000-2003中读取数据。
因为地址指向该变量单元,所以将地址形象地称为“指针”。
那么如何实现地址的记录呢?
取出a变量,可以记录2000这个首地址,和a这个数据类型存储时候地长度,就是4。这两个量地记录就可以实现a的访问。
所以C语言中的地址,包括位置信息和类型信息。
在C语言中,一般是通过变量名引用变量的值。所以如果a对应的就是2000这个地址,访问起来就十分方便。这也是C语言的一大特点。
总结:
编译时,系统为变量分配内存,并建立变量名和地址的对应表。
执行语句时,通过变量名,找到相应的地址,按照数据类型读出变量的值。
(2)直接访问 和 间接访问
直接访问:按变量地址存取变量值。
scanf("%d",&x); printf("%d",x); k=a+b;
间接访问:将变量i的地址,存放在另一个变量中。
在访问的时候,先找到i_pointer,从中取出 i 的地址(2000),然后找到2000,2001的字节,取出 i 的值。
(3)指针:
一个变量的地址,称为该变量的指针。
如果有一个变量专门用来存放另一变量的地址(指针),则这个变量称为“指针变量”。
(4)程序示例与分析
1 #include<stdio.h> 2 int main() 3 { 4 int a=100,b=10; 5 int *pointer_1,*pointer_2; 6 pointer_1 = &a ; 7 pointer_2 = &b ; 8 9 printf("a=%d\n",a); 10 printf("%d\n",*pointer_1); //输出a的值 11 12 printf("%p\n",&a); //输出的是a变量的地址 13 printf("%p\n",pointer_1); //通过指针变量ap输出a变量的地址 14 printf("%p\n",&pointer_1); //指针变量ap本身自己的地址 15 return 0; 16 }
5:* 表示该变量的类型为 指针型变量 。
6:pointer_1分配的内存中,存储着a变量的地址
9:直接访问法,访问a的值。步骤:a表示首地址,类型决定字节数,从内存中读取出a的值。
10:间接访问法,*pointer,读取出&a(即a的地址)。然后因为要输出%d,所以再去内存区,按照读出的地址,读取数据
12:&a是a的地址,要求输出地址格式的数据,正好符合。
13:pointer存储的是&a,实际也是地址。
14:&pointer意为,得到地址直接输出,不去读取pointer的值。
(5)一个变量的指针,包括 以存储单元编号表示的纯地址 和 它指向的存储单元的数据类型。
8.2 指针变量的定义和使用
(1)定义一般形式:
基类型 *指针变量名
float a; float *it_1 = &a;
- * 表示该变量的类型为 指针型变量 。
- 指针变量名是it_1.
- 必须指定基类型。(指针类型是基本数据类型,派生出来的类型)
- 指针变量只能存放地址,不可以直接给他赋值某个整数
- &a和p,实际上都包括地址和类型信息。应使赋值号两边类型一致
- 指针变量的值——指针变量的指向——修改temp
- 指针变量所指变量的值——修改*temp
(2)引用:
#include<stdio.h> int main() { int a=100; int *pointer_1; //1 给指针变量赋值 pointer_1 = &a ; //2 间接引用变量a printf("%d\n",*pointer_1 ); *pointer_1 ++; //等同于a++ *pointer_1 = 1; //等同于a=1 //3 引用指针变量的值 pointer_1 = &a; printf("%X %p",pointer_1,&a); //输出a的地址 //x是十六进制小写,X是十六进制大写,o是八进制小写 //p是输出地址 return 0; }
(3)运算符
& 取地址运算符
* 指针运算符
(4)指针变量作为函数参数
作用:
将一个变量的地址传送到另一个函数中。
相比起将变量作为参数,将指针变量作为参数,可以在函数中改变变量的值。
而且相比起返回值,使用指针变量作为参数,可以得到多个变化过的值。
示例:
将 a,b的地址 分别赋给 int *p1,int *p2 。
形参是指针型变量。
在调用时,采取的是”值传递“。将&a,&b传给p1,p2。
修改时,p1和p2的值都没变,*p1修改实际是通过间接访问,修改了a的内存中的值。
#include<stdio.h> int main() { int a,b; scanf("%d%d",&a,&b); void swap(int *p1,int *p2); swap(&a,&b); printf("%d %d",a,b); return 0; } void swap(int *p1,int *p2) { int temp; temp = *p1 ; *p1 = *p2 ; *p2 = temp ; //这个函数实际上,都是值的相互赋值,所以最后不用加取地址符。 }
错误版本1:
已知*p1就是a,*temp就是temp这个指针变量,所指向的变量。
因为没有给temp赋初值,也没有制空。
所以这时候temp是个野指针。它会随机附上内存地址的一段值,甚至有时候会覆盖前面所用过的内存空间。
void swap(int *p1,int *p2) { int *temp; *temp = *p1 ; //这个句子有问题 *p1 = *p2 ; *p2 = temp ; }
错误版本2:
企图通过改变 指针形参的值 改变 指针实参的值。
void swap(int *p1,int *p2) { int *temp; temp = p1 ; //temp指向a p1 = p2 ; //p1指向b p2 = temp ; //p2指向a //最后p1,p2指向的变量交换了,但是a,b的值没有经过改变 }
指针运算:
(6)&*it
从右向左进行结合。 *it 就变量a。
…&*it 则是 &a。
(7)*&it
&a是a的地址,*&a则是&a所指向的变量,也就是a
(8)(*it )++
相当于a++
(9)*(it++)
先做*it运算,得到a。整体相当于a++
8.3 数组与指针
(1)指针运算:
当指针指向一个数组元素的时候,可以对指针进行如下操作:
- p+k p-k p++ p--
- p1-p2 要求 两个指针指向同一个数组的元素时
- *p++ 等同于 a[i]++
- * ( p++ ) 等同于 a[i++]
- * ( ++p ) 等同于 a[++i]
- ++(*p) 等同于 ++a[i]
(2)用指针 引用 数组元素:
下标法:如a[i]
指针法:如*(a+i)或*(p+i)
- a[ i ] 与 *(a+i) 实际执行效果相同,但由于计算元素地址操作,较为费时间
- *p,p++ p++自加操作速度快,能够大大提高程序效率
说明:
- [ ] 实际上是变址运算符,a[i]按照a+i计算地址。
- a作为数组名,代表数组首元素的地址,是一个常量。a++这种操作无意义。
- a[ n ] 虽然并不会出错,但是运行结果不可预期。
- 指向数组元素的指针向量p,也可以带下标 p[ i ]
(3)函数传参有三种方式:值传递,地址传递,引用传递。
①地址传递,形参存放地址所以一定为指针变量。指针变量和实参都指向同一个地址。
所以,被调用函数中对形参指针所指地址中的内容,的任何改变,都会影响到实参。
#include<stdio.h> int main() { void swap(int *,int *); int a,b; swap( &a , &b ); return 0; } void swap(int *m,int *n) { };
②引用传递,在形参调用前,加入"&"符号。引用为实参的别名,和实参是同一个变量,则值相同,修改也相同.
(4)用数组名作函数参数
编译时是将arr按指针变量处理的。
实参——数组名或指针变量
f(a,n);
f(p,n);
形参——指针变量或数组名。以下两种写法实际相同。
void f(int arr[],int n) { } void f(int *arr,int n) { }
在函数执行过程中,指针变量类型的形参,可以再次被赋值。
(5)多维数组的地址:先行后列
(6)应用及注释
// p p2 纯地址相同,但是基类型不同 int *p = a[0]; //基类型是整形数据 //此时P相当于 &a[0][0] p++; //p相当于 &a[0][1]。 int (*p1)[n]; //基类型为n个整形数据组成的一维数组 int *p1 = a; //同上 p1++; //p1指向a[1][0];2363569
//对于a数组中的任一元素a[i][j],指针的一般形式如下: int *pij = *(p1+i)+j; //元素a[i][j]相应的指针表示为: int *pij = *(p+i*N+j); //同样,a[i][j]也可使用指针下标法表示,如下: int *pij = p[i*N+j]; //对于二维数组a,其a[0]数组由a指向,a[1]数组则由a+1指向,a[2]数组由a+2指向,以此类推。 //因此,*a与a[0]等价、*(a+1)与a[1]等价、*(a+2)与a[2]等价,┅,即对于a[i]数组,由*(a+i)指向。 //由此,对于数组元素a[i][j],用数组名a的表示形式为: *(*(a+i)+j) //指向该元素的指针为: *(a+i)+j
(7)指针数组
int *p[N];
8.4 通过指针引用字符串
(1)存放字符串并且输出
#include<stdio.h> int main() { //两种定义方法 char ch[] = "I love China !"; char *ch = "I love China !"; printf("%s\n",ch); printf("%c\n",ch[7]); return 0; }
(2)字符数组和字符指针变量的区别:
- 字符数组由若干个字符元素组成;字符指针变量中存放的是地址(即字符串第一个字符的地址)。
- 赋值方法:
char str[14]; str [] = { "I Love China!" }; char *a ; a= "I Love China!";
- ·定义一个字符指针变量时,给指针变量分配内存单元。但如果未对它赋予一个地址值时,则它并未具体指向一个确定的字符数据。
char str[10]; scanf("%s",str); //正确 char *a; scanf("%s",a); //错误 char *a,str[10]; a=str; scanf("%s",a); //正确
(3)若定义了一个指针变量,并使它指向一个字符串,就可以用下标形式,引用指针变量所指的字符串中的字符。
char *a = "I love China!"; int i; for(i=0 ; a[i]!='\0' ; i++ ) printf("%c",a[i]);
总结:
也正是因为C/C++有指针的特性,所以他是最自由的。可以对内存空间实现直接的操作,这也是省时和高效的原因。(贴近底层操作系统)
这也带来了危险,比如指针偏移内存泄露。不用时记得赋值NULL
例题:寻找d[2][4]中的最大元素的值
#include<stdio.h> using namespace std; int d[2][4]; int main() { for(int i=0;i<2;i++) for(int j=0;j<4;j++) scanf("%d",&d[i][j]); int *p = d[0] ; int i,j; int *nw; //用指针代替循环 //结束条件也可以写成 nw < &a[2][4] for(nw=d[0],p=d[0] ; nw<d[0]+8 ; nw++ ) if( *nw > *p ) p=nw; //枚举整数类型以循环 for(i=0,p=d[0] ;i<2;i++) for(j=0;j<4;j++) if(d[i][j] > *p ) { p = &d[i][j]; } printf("%d\n",*p); return 0; }
暂时完结,深入的内容准备暑假再阅读。
参考资料:
《C程序设计》第八章
https://blog.csdn.net/qq_33573235/article/details/79530792