1. 指针的特点
- 编写代码更加简洁高效
- 指针可以直接访问内存,要访问合法的内存地址(不要越界访问)
- 指针可以直接操作硬件(51单片机,ARM开发板)
- 变量、数组:为了数据存储
指针:为了高效访问数据,间接访问
2. 基本概念
内存(memory)
- 程序运行后,数据暂存的场所。32bit下,0~4G。
- C语言中,操作内存的时候,最小单位是1byte(字节)。
- code(代码段),data(数据段),heap(堆),map/share(C库函数),stack(栈:局部变量、函数参数、返回地址)
地址
- 由于地址空间相对较大,C语言中最小操作单位1byte。
- 为了方便访问内存,给每个内存都分配了一个编号,叫内存的地址。
指针(指针变量)
- 专门存储地址的变量,叫指针变量,也叫指针。
3. 指针相关运算符
1)& 取地址运算符
&(取地址运算符):获得变量在内存空间的地址
- 只能给左值(变量)取地址
- int a;&a;&a是一个表达式。 它的值表示的是这个变量在内存空间的首地址。类型是int *。
double num = 0;
#// 0x123456 double *
2)* 解引用
只能是指针类型才能解引用,先找到对应的内存地址,获得内存地址空间中的内容。
int a = 20;
int *p = &a;// int * 不是解引用,是类型说明符,意思是p里只能装int型的地址。
a;//20 直接访问
*p;//20 解引用 值:对应内存空间中的数据,类型 int
*p = 10; //给a写入数据,左值
int b = *p;// 右值,读取a中的数据
*&a;// 20 *和&是相反操作的运算符
int p;
int *p;// p int ——>int*
*p;// int * ——> int
4. 指针的定义
基类型 * 指针名;
int* p1; // * 不是解引用 类型说明符 int *
char *p2; // char*
double * p3; //double *
float* p4; //float *
5. 指针的初始化
指针需要初始化后,才可以使用。指针必须和某个内存区域关联后,才可使用。
野指针:
未经初始化的指针,或指针指向的内存空间,是一个已经被释放的空间的指针。
int *p;// p中存储的地址,是一个随机值。从逻辑上说,这个指针不能读也不能写。
空指针:
NULL(宏定义),在stdio.h中。
int *p1 = NULL;//空指针,NULL == ((void*)0) 指针已初始化,稍后才会关联一个 变量的地址。未关联变量地址前,也不能读写。
int main()
{
int a = 5;
printf("address:%p\\n", &a);
int *p1;
char *p2;
double *p3;
float * p4;
// printf("p1:%d\\n", *p1);
// 野指针,可能会段错误
//指针大小
printf("int *p1 size:%lu\\n", sizeof(p1));
printf("int *p2 size:%lu\\n", sizeof(p2));
printf("int *p3 size:%lu\\n", sizeof(p3));
printf("int *p4 size:%lu\\n", sizeof(p4));
int *p5 = NULL; //空指针
int *p6 = &a;
printf("p6:%p\\n", p6);
printf("p6:%p\\n", &a);
printf("a value:%d\\n", *p6);//通过指针,间接读取a变量的值
*p6 = 20;//修改变量
printf("new a value:%d\\n", *p6);//通过指针,间接读取a变量的值
printf("a value :%d\\n", a);//直接访问a变量的值
return 0;
}
/*
输出
address:0x7ffcef3a9584
int *p1 size:8
int *p2 size:8
int *p3 size:8
int *p4 size:8
p6:0x7ffcef3a9584
p6:0x7ffcef3a9584
a value:5
new a value:20
a value :20
*/
6. 指针的算术运算
+ – * / % ++ —
int a = 20;
int *p = &a;
int *p2 = p1+1;
+
p+1
在p指针指向的地址的基础上,再加一个sizeof(基类型)个字节(如int型就加4byte)。那么p指针就指向当前元素的下一个元素。
如果指针需要+1,-1, 一般和数组操作的时候,才会使用。
+:p+1(数组)
两指针相加,无意义 p(存储一个变量的地址)+q(存储另外一个变量的地址)==无意义

–
p – 1
p应该指向一个数组,操作完后,指针应该落在数组范围以内。
int a[10] = {1,2,3,4,5,6,7,8,9,0};
int *p = a + 1;
int *q = a + 5;
printf("q-p %ld\\n", q-p);
两个地址相减,前提这两个指针向同一个数组。
意义:相差的元素个数。 如果这两个指针其他不同的地方,相减无意义。
* / %
对指针,无意义
++和–
++ p++
先获得当前地址,加上sizeof(基类型)。计算的结果再写回指针变量本身。
— p–
先获得当前地址,减去sizeof(基类型)。计算的结果再写回指针变量本身
不同类型指针再加1:
- char * 偏移量1byte sizeof(基类型)
- int * 偏移量4byte
- float * 偏移量4byte
- double * 偏移量8byte
不同类型执行解引用操作:
char * 从指针存储地址开始,往后(地址变大的方向)取1个字节的内容
int * 从指针存储地址开始,往后(地址变大的方向)取4个字节的内容
float * 从指针存储地址开始,往后(地址变大的方向)取4个字节的内容
double * 从指针存储地址开始,往后(地址变大的方向)取8个字节的内容
char str[100] = "hello";
char * p = str;
*p
其他操作
指针在使用的时候,有两个概念,一个是指针变量本身,一个是指针指向的内容

对指针变量的修改

int main()
{
int b[5] = {0};
int a[5] = {0};
int *p = a;
*p = 123; //修改指针指向的内容
p = b; // 修改指针的指向
return 0;
}
7. 指针和数组
1)数组的数组名
- 数组的数组名是一个指向数组中第一元素的指针常量。
- a 存储的地址,不能发生变化。
- 数组名的本质就是一个指针常量(加了一些限制:const)。
数组名与普通指针访问数据的注意事项:
int main()
{
char str[100]="hello";
char * p = str;
/*
while(*p)
{
printf("%c",*p);
p++; // 可以++,– 操作, p本身被修改
}
*/
/*
while(*str)
{
printf("%c",*str);
str++; // 数组名一个指针常量,本身不可以++,– 。当数组定义好后, str 是一个地址
标号,和开辟的内存空间的编号,终身绑定。
}*/
show(str);
}
2)int * 和 int[]
int * 约等于 int[],只有以下两点不同用法:
sizeof 数组:int a[50];sizeof(a) == sizeof(int) × 50byte
sizeof 指针:int *p;sizeof(p) == 8byte 任意指针都是8个字节(64bit)
int * 执行取地址操作,会变成int** ,是二级指针,即存储地址的地址。
int[] 执行取地址操作,即int(*p)[],是数组指针,指向二维数组中的一行。

int a[10] = {1,2,3,4}; //2000
int b[10] = {2,3,4,5,6,7}; //3000
a = b; //错误,a中存储的地址不能改变。

3)使用指针访问,数组的元素
int a[5]={1,2,3,4,5};
printf("arrray addr %p\\n",a); //a就是一个指针。 存储的是 a[0]地址
printf("a[0] %p\\n",&a[0]);
printf("a[1] %p\\n",&a[1]);
int *p =a ; // p指针和 数组进行关联 , p 中存储了a[0]的地址。
printf("a[0] %d\\n",a[0]);
printf("point *(p+0) %d\\n",*(p+0));
printf("*(a+0) %d\\n",*(a+0));
printf("point p[0] %d\\n",p[0]);
printf("a[0] addr %p\\n",&a[0]);
printf("point addr *(p+0) %p\\n",&(*(p+0)));
printf("*(a+0) addr %p\\n",&(*(a+0)));
printf("point addr p[0] %p\\n",&p[0]);
printf("a[1] %d\\n",a[1]);
printf("point a[0] %d\\n",*(p+1));
访问方式
a[n] —> *(p+n) –>*(a+n)—> p[n]
8. 函数使用指针作为参数
1)值传递
形参是实参的复制品,只能读取实参的数据,在被调函数内部,不能通过修改形参而改变实参。基本数据类型的变量。
2)地址传递
形参会接收到实参传递过来的地址,就可以在被调函数的内部,通过间接访问的方式,通过修改形参,进而修改实参。

9. 字符数组的传参——地址传参
使用指针访问数据,比使用数组方式访问要高效、简洁。
mystrlen
int mystrlen(char *p1)
{
int len = 0;
while(*p1)
{
len++;
p1++;
}
return len;
}
mystrcopy
int mystrcpy(char *p2, char *p1)
{
while(*p2 = *p1)
{
printf("%c", *p2);
p1++;
p2++;
}
}
mystrcmp
int mystrcmp(char *p1, char *p2)
{
while(*p1 == *p2 && *p1)
{
p1++;
p2++;
}
return *p1 – *p2;
}
mystrcat
int mystrcat(char*p, char*q)
{
while('\\0'!=*p)
{
p++;
}
while(*p = *q)
{
p++;
q++;
}
}
mystrrev
int strrevert(char * str)
{
int len = strlen(str);
int i = 0 ;
for(i=0;i<len/2;i++)
{
char c = *(str+i);
*(str+i) = *(str+len-1-i);
*(str+len-1-i) =c;
}
return 0;
}
10. 指针函数和函数指针
1)指针函数
函数的返回值为指针类型的函数,叫指针函数。
- 注意:不要返回局部作用的数据(局部变量、局部数组)作为另一个函数的参数。
mystrncpy
char *mystrncpy(char *dst, char *src, int n)
{
char *temp = dst;
while((*dst = *src) && –n)
{
dst++;
src++;
}
if(0 == n)
{
*(dst+1) = '\\0';
}
return temp;
}
int main()
{
char src[50] = {0};
char dst[50] = {0};
printf("input str:");
gets(src);
char *p = mystrncpy(dst, src, 5);
printf("strncopy:%s\\n", dst);
return 0;
}
/*
input str:hello
strncopy:hello
*/
mystrncat
#include<stdio.h>
char *mystrncat(char *str1, char *str2, int n)
{
while(*str1 != '\\0')
{
str1++;
}
while((*str1 = *str2) && –n)
{
str1++;
str2++;
}
return str1;
}
int main()
{
char str1[50] = {0};
char str2[50] = {0};
printf("string1:");
gets(str1);
printf("string2:");
gets(str2);
mystrncat(str1, str2, 4);
printf("contanct:%s\\n", str1);
return 0;
}
/*
string1:respon
string2:sibility
contanct:responsibi
*/
mystrncmp
#include<stdio.h>
int mystrncmp(char *str1, char *str2, int n)
{
while(*str1==*str2 && –n && *str1!='\\0')
{
str1++;
str2++;
}
return *str1 – *str2;
}
int main()
{
char str1[50] = {0};
char str2[50] = {0};
printf("input string1:");
gets(str1);
printf("input string2:");
gets(str2);
int ret = mystrncmp(str1, str2, 4);
if(ret != 0)
{
printf("4th word is not same\\n");
}
else
{
printf("4th word is same\\n");
}
return 0;
}
/*
input string1:hhhhhhhhh
input string2:hhhhkkkkk
fore4 word is same
input string1:hhhhhh
input string2:hhhkhh
fore4 word is not same
*/
2)函数指针
本质是指针,存储某一个函数的地址。函数名本质是一个指针,存储了一个地址,指向代码段中,对数据操作的代码。
int add(int a, int b)
{
}
int (*p) (int, int) = NULL; // 定义指针,初始化
p = add; // 函数add的地址给到指针p
int ret = p(a, b);
printf("%d", ret);
应用:降低代码的耦合度
回调函数、函数设计者、函数的调用者:
- 作为函数设计者,希望显示数据的时候,可以更加多种规则显示。所有需要主调者传入规则函数。需要使用函数指针进行解耦。这里的调函数就是回调函数
案例:低耦合调用加减乘除
#include<stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a – b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int num1 = 0;
int num2 = 0;
printf("input num1:");
scanf("%d", &num1);
printf("input num2:");
scanf("%d", &num2);
int (*p) (int, int) = NULL;
int choose = 0;
printf("input: 1(+) or 2(-) or 3(*) or 4(/)\\n");
scanf("%d", &choose);
switch(choose)
{
case 1:
p = add;
break;
case 2:
p = sub;
break;
case 3:
p = mul;
break;
case 4:
p = div;
break;
default:
printf("input error\\n");
}
int ret = p(num1,num2);
printf("result:%d\\n", ret);
return 0;
}
/*
input num1:4
input num2:2
input: 1(+) or 2(-) or 3(*) or 4(/)
4
result:2
*/
12. 二级指针
概念:指向指针变量的指针。
int a =20; // int
int *p = &a; // int* = int *
int** q = &p; // int **
printf("&a is %p\\n",&a);
printf(" p->a is %p\\n",p);
printf("q->p is %p\\n",q);
printf("a is %d\\n",a);
printf("*p is %d\\n",*p); // int * –>* ->int
printf("*q is %p\\n",*q);// &a -> int*
printf("**q is %d\\n",**q);// &a -> int
应用:
13. const 指针
const 常量
int a = 20;
const int b = 10; // b 叫作只读变量
const int c; // 错误
c = 20; // 因为是只读变量,需要在定义的时候就初始化,否则后续赋值会报错。
//常量指针
//指针指向的内容不能发生变化,p1与p2等价
const int *p1;
int const *p2;
//指针常量
//指针本身不能变化,指针的指向也不能变
int *const p3;
//指向常量的常量指针
//指针本身不能变,指针指向的内容也不能变
const int * const p4;
char str1[100] = "hello";
char str2[100] = "world";
const char *p = str; // 常用
p[0] = 'a'; // 错误,不能通过p修改str的内容
str1[0] = 'a'; // 正确
p = str2; // 正确,表达内容不能变,没有表示本身不能变
char* const p2 = str; // 定义的时候,就要初始化
p2[0] = 'a'; // 正确,指针本身不能变,指向的内容可以修改
p2 = str2; // 错误,p2本身不能变化
const char *const p3 = str; //指针本身不能变,内容也不能变
// p3[0] = 'a';
// p3 = str2;





