【1】指针
1.概念
地址:内存中每个自己都有唯一的编号,这编号就是地址。(门牌号)
指针:就是地址。
指针变量:存储指针(地址)的变量就是指针变量。
2.定义格式:
[存储类型] [数据类型] *指针变量名;
(auto) int *p;
* :在定义指针变量时,*仅起标识作用。
数据类型:指针的数据类型要和指向的数据类型统一。
#### 3.指针的应用:
##### 1)指针赋值运算
**a.指针指向普通变量**
```
* 将普通变量的地址赋值给了指针变量,指针变量存储普通变量地址即为指向该变量。
int a = 10;
int *p1 = &a; //&取地址符:取出变量的地址
int *p2 = NULL; //指针指空,防止出现野指针,没指向对象的指针就叫野指针。
p2 = &a; //正确
*p2 = a; //错误,因为指针指空,这相当于给空地址赋值,导致段错误。
int *p3; //野指针,指针变量里是随机值,指针随机指向一个地址,造成非法访问,导致段错误。
int b;
int *p4 = NULL;
p4 = &b;
*p4 = 10;
//正确写法 ,相当于给变量赋值。
```
b.**指针指向数组**
```
将数组的首个元素地址(首地址)赋值给指针变量。
int num[10] = {0};
int *p1 = num;
int *p2 = &num[0];
c.多个指针指向同一个地址(指针之间互相赋值)
将一个指针变量里存储的地址赋值给另一个指针。
int a = 10;
int *p1 = &a;
int *p2 = p1;
int *p3 = NULL;
p3 = p1;
//p1、p2、p3都指向变量a.
//*:取值符 *指针变量 = 取指针变量存储的地址上的值
```
**2)指针的算术运算**
```
++ --
p++ : 指针向高地址方向移动一个数据
p-- : 指针向低地址方向移动一个数据
(具体移动几个字节根据指向的数据类型来判断)
```
3)指针的关系运算
== > < >= <= !=
指针的关系运算本质上是比较的地址的高低
指向高地址的指针大于指向低地址的指针
注:指向不同数据类型或不同区域的指针之间没有关系运算的意义。
int a[5] = {1,2,3,4,5};
int *p1 = &a[0];
int *p2 = &a[2];
//p2 > p1
4)& 取址符 : 取变量的地址
取值符 : 取地址上的值
&和*互为逆运算,可以抵消。
int a[5] = {1,2,3,4,5};
int *p = NULL;
p = a;
*p = *a = *(&a[0]) = a[0] = 1
访问a[1]的值:
a[1] *(a+1)
p[1] *(p+1)
访问a[1]的地址
&a[1] a+1
p[1] *(p+1)
约定地址的等级比值的等级高
a[1] -> 升级成地址 &a[1] 升级 &
a[1] -> 升级成地址 a+1 升级 去掉[]
&a[0] -> 降级成元素 *(&a[0]) -> a[0] 降级 *
a 数组首地址 -> a+0 -> 降级成元素 a[0] 降级 加上[]
4.指针的大小
指针的大小与指针的数据类型无关,与指针的寻址空间有关。
32位操作系统中,指针恒为4字节。
32位的操作系统,有效寻址空间是0~4G,地址都是一个8位的十六进制数,占用4字节。
8位16进制 = 32位2进制 = 4字节
8位2进制 = 1字节
int a[10];
int *p1 = a; //sizeof(p1) = 4
char b;
char *p2 = &b; //sizeof(p2) = 4
double f;
double *p3 = &f; //sizeof(p3) = 4
练习:从终端输入一串字符,将其中的大写字母转换成小写字母,小写字母转换成大写字母,
其他字符全部转换成*,输出该字符串。(用指针实现)
练习:用指针实现字符串倒置。
5.指针的修饰
1)const常量化
功能:只读修饰,变量经过只读修饰后无法被更改。
const int a = 10;
a = 20; //error: assignment of read-only variable ‘a’
//经过const修饰的变量无法被更改。
const修饰指针
情形一:
int a = 10;
int b = 20;
const int *p = &a; //对于const来说修饰的是*p
int const *p = &a; //同上
*p = 20; //报错,*p被const修饰无法更改
p = &b; //正确,p的指向可以改
总结:可以通过更改p的指向间接更改*p的值,但无法通过*p直接更改。
情形二:
int a = 10;
int b = 20;
int * const p = &a;
*p = 20; //正确,*p可以被更改。
p = &b; //错误,p被const修饰,p的指向无法更改。
总结:可以更改*p,但无法更改p的指向。
#####
2)void空类型
void *p = NULL; //空类型指针可以指向任意一种类型的变量。
示例:
void *p = NULL;
int a = 10;
p = &a;
printf("*p = %d\n",*(int *)p);
char b = 'A';
p = &b;
printf("*p = %c\n",*(char *)p);
double c = 3.1415926;
p = &c;
printf("*p = %.2lf\n",*(double *)p);
【2】二级指针
1.概念:
指向一级指针的指针称为二级指针
示例:
int a = 10;
int *p = &a; //一级指针
int **q = &p; //二级指针
int ***s = &q; //三级指针
int x = &s; //四级指针
访问a的值:
a *p (**q)
访问a的地址
&a p *q
//二级指针q里面保存的是一级指针p的地址,
*q指的是p里面的值,即a的地址,
q指的是a的值。
2.指针自加运算
p++ :++在后,先取p的值(指p变量里保存的地址)进行打印,再让指针向后移动。
++p :++在前,先让指针向后移动一个数据,再取p的值进行打印。
*p++ : 先取出p的值参与取值运算(*),再让指针向后移动一个数据。
*(p++): 先取出p的值参与取值运算(*),再让指针向后移动一个数据。
(*p)++:先取出p的值参与取值运算(*),让指针指向的值自加1.
++*p:取出p指向的地址上的值进行自加1
++(*p):取出p指向的地址上的值进行自加1
*++p :先让指针向后移动一个数据,再取出p指向的地址上的值。
*(++p) :先让指针向后移动一个数据,再取出p指向的地址上的值。
【3】数组指针
1.概念:
本质上是个指针,指向数组(二维数组)的指针称为数组指针。
2.定义格式:
[存储类型] [数据类型] (*数组指针名)[列数];
(auto) int (*p)[i];
示例:
数组指针指向二维数组(指向了二维数组的行地址)
//数组指针是一个二级指针
p 行地址
*p 列地址 元素地址
**p 元素值
int a[2][3] = {{1,2,3},{4,5,6}};
int (*p)[3] = a;
访问元素a[i][j]的值:
直接访问: a[i][j] *(a[i]+j) *(*(a+i)+j)
间接访问: p[i][j] *(p[i]+j) *(*(p+i)+j)
访问元素a[i][j]的地址:
直接访问: &a[i][j] a[i]+j *(a+i)+j
间接访问: &p[i][j] p[i]+j *(p+i)+j
【4】指针数组
1.概念:
本质上是个数组,存放指针(一级)的数组称为指针数组。
定义格式:
[存储类型] [数据类型] *指针数组名[大小];
(auto) int *p[i];
大小:元素个数
应用示例:
1.存放普通变量的地址
int a = 10,b = 20,c = 30;
int *p[3] = {&a,&b,&c};
通过指针数组名p访问b的值:
*p[1] *(*(p+1))
通过指针数组名p访问b的地址:
p[1] *(p+1)
注:*(*(p+1)) : p是指针数组名即数组中第一个元素的地址,p+1是数组中p[1]这个元素的地址,
*(p+1)是p[1]这个元素的值即&b,*(*(p+1))则为b的值。
2.存放二维数组每行首个元素的地址
int a[2][3] = {1,2,3,4,5,6};
int *p[2] = {&a[0][0],&a[1][0]}; //指针数组名相当于二级指针
int *p[2] = {a[0],a[1]};
通过指针数组p访问元素a[i][j]的值:
p[i][j] *(p[i]+j) *(*(p+i)+j)
通过指针数组p访问元素a[i][j]的地址:
&p[i][j] p[i]+j *(p+i)+j
注:p是指针数组,存放的是二维数组每行首个元素的地址,p[1]是a[1]即a[1][0]这个元素的地址,
p[1]+1即为a[1][1]的地址,*(p[1]+1)即为a[1][1]的值。
指针数组中存放的是一级地址,p[1]即为数组中下标为1的元素值,即为&a[1][0]
p[1]+1 即为 &a[1][0] + 1 => &a[1][1]
*(p[1]+1) => a[1][1]
3. #### 存放字符串
```
3. char *p[3] = {"farsight","jinan","yyds!"};
打印"yyds!"字符串:
printf("%s\n",p[2]);
printf("%s\n",*(p+2));
打印‘!’字符:
printf("%c\n",*(p[2]+4));
printf("%c\n",*(*(p+2)+4));
//注:p[2]是“yyds!”的首地址,即'y'这个字符的地址,
p[2]+4就是'!'这个字符的地址,
*(p[2]+4)就是'!'这个字符。
*(*(p+2)+4)
```
4.命令行传参
int main(int argc, const char *argv[])
const char *argv[] : argv是一个指针数组,用于存放命令行传递的字符串。
int argc :argc是一个整型变量,用于记录命令行传递参数的个数。
示例:
#include<stdio.h>
int main(int argc, const char *argv[])
{
puts(argv[0]);
puts(argv[1]);
printf("argc = %d\n",argc);
return 0;
}
执行: ./a.out hello
打印结果:
./a.out
hello
argc = 2
练习:仿写strncpy的功能
例如:s1 : hello
s2 : abc
n : 2
输出:abllo