一、指针的基础
1.指针的感念
(1)地址:内存单元的编号,指针就是地址,即指针就是内存单元编号
(2)指针也是一种数据类型,指针类型,专门用来处理地址数据
2.指针变量定义及引用
语法:
基类型 * 指针变量名
(1)基类型
数据类型 :整型、浮点型、字符型、指针类型、结构体类型 、函数类型
作用:表示该指针类型所指向的内存空间存放什么类型的数据
(2)* //定义时表示此时定义的是一个指针类型的变量
(3)指针变量名 //符合标识符命名规则
int * p; //pointer
int a = 10; //a所在的空间是用来存放int类型数据的
float b = 10.0;
int *p = &a;
int *p = &b;//不可以,指针类型与b的数据类型不一致
&a //表示获得a所在空间的首地址,表示获得了一块可以存放int类型数据的内存空间的地址
指针类型
int *p; int * 整体叫指针类型
int* 含义 首先表示一个指针类型,指向int型数据的指针类型
指针变量的引用
int a = 20;
int *p = &a //p指向a,p中保存了a的地址
* 指针运算符为单目运算符,运算对象只能是指针(地址)
*p 表示访问p所指向的基类型的内存空间
*p 为间接访问,通过a访问的为直接访问
运算过程:(1)首先拿出p中的地址,到内存中定位(2)偏移出sizeof(基类型)大小的一块空间(3)将偏移处的这块空间,当做一个基类型来看(*p运算完的效果)
运行效果:相当于就是一个基类型的变量 *p<=>a
int a;
a的数据类型为int;&a的数据类型为int*(地址这种数据对应的数据类型为指针类型)
指针变量初始化
如果指针变量没有初始化,此时是随机值,称为野指针。初始化可以让指针变量有明确的指向
int a = 10;
int *p = &a;
int *p = NULL; //NULL就是0号地址---空指针
//赋值:
int *p;
p = NULL;
int a;
int *p;
p = &a;
int *p,q;//p是指针类型int*,q是int型
int *p,*q //此时表示定义了两个int*类型的变量p和q
注意:定义时候的*是修饰变量名的,表示定义的是一个指针类型的变量
指针的用途:指针可以实现被调修改主调
使用方法:(1)要修改谁,就把谁的地址传过去(2)被调函数中,一定要有对应的*p运算(3)传过去的实参地址,一定要对应一块有效的内存空间
#include<stdio.h>
int addOne (int *n)
{
*n = *n + 1;
return *n;
}
int main(void)
{
int m = 1;
int ret = addOne(&m);
printf(" m = %d,ret = %d\n",m,ret) ;
return 0;
}
//运行结果为:m = 2,ret = 2
3.指针作为函数参数
形参---指针类型变量,用来接收实参(实参是要操作的内存空间的地址)
实参---要修改谁,就把谁传过去
注意:被调函数中一定要有*p运算
值传递:只是实参数据赋值给了形参
地址(指针)传递:传的是地址--可以实现被调修改主调
int *n;
int m = 1;
addOne(n); //传的是指针变量n里的值,是地址,但是此时n为野指针,指向是随机的
addOne(&m); //传的是m的地址
int *n = &m;//此时n为m的地址
printf("%d\n",*n);
二、指针进阶
1.指针+一维整形数组
int a[5]; //一维整形数组
// 数组名的类型是数据类型int[5],数组名的值是数组首元素的地址是常量,故不能做自 增运算
(1)定义一个什么类型的指针变量
int *p = a;
int *p = &a[0];
(2)谁能代表数组所在空间的首地址
数组名---数组首元素的地址
这个地址值的类型
a<=>&a[0] // a[0]的数据类型为int型
//&a[0]地址的类型为int *
=> int *p = a; //表示p指向数组a
*p <=> a[0]
指针的运算:
&
*
p+1
p-1
p++
p--
指针比较 > >= <= !=
p-q 表示差了多少元素(基类型),必须是同一类型的指针
p+q 不行,没有含义
int a = 10;
int *p = &a;
printf("*p = %p\n",&*p); //运行结果:*p = 0x7ffd14ae25cc
printf("*p = %p\n",*&p); //运行结果:
printf("*p = %p\n",*&a); //运行结果:
printf("*p = %p\n",&*a); //运行结果:
int a[5] = {2,5,6,8,9};
int *p = a;
printf("p = %p\n",p);
p++;
printf("p = %p\n",p);
printf("a = %p\n",a);
printf("&a[0] = %p\n",&a[0]);
printf("p + 1 = %p\n",p+1);//加n表示跳过了n个基类型
#include<stdio.h>
int main(void)
{
int a[5] = {2,5,6,8,9};
int *p = a;
int i = 0;
for (i = 0;i<5;i++)
{
printf("a[%d] = %d\n",i,*(p+i));
//printf("a[%d] = %d\n",i,*(a+i));//a为首地址a[0]
printf("a[%d] = %d\n",i,a[i]);
printf("a[%d] = %d\n",i,i[a]);
printf("a[%d] = %d\n",i,*a++);//a为常量不能作自增
printf("a[%d] = %d\n",i,*p++);
//以上情况运行结果都是一样的
}
return 0;
}
*(p+i)<=>a[i]<=>*(a+i)
i[a]<=>*(i+a)
数组作为函数的参数
形参---数组形式//本质上是一个指针类型的变量:int *a
数组长度:int len
实参---数组名 数组名代表首地址
#include <stdio.h>
void getMaxArray(int *a,int len)
{
int i;
int max = *a;
for (i = 1; i < len ;i++)
{
if(max < *(a+i))
max = *(a+i);
}
printf("max = %d\n",max);
}
void printArrayP(int *begin,int *end)
{
while (begin<=end)
{
printf("%d",*begin++);
}
putchar('\n');
}
void swap(int *a,int *b)
{
int t = *a;
*a = *b;
*b = t;
}
void reverseArray(int *begin,int *end)
{
while(begin<end)
{
swap(begin,end);
begin++;
end--;
}
}
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9,10};
int len = sizeof(a)/sizeof(a[0]);
reverseArray(a,a+len-1);
printArrayP(a,a+len-1);
printArrayP(a+3,a+8);
getMaxArray(a,len);
return 0;
}
2.指针+一维字符型数组
char s[] = "hello";
char *p = s;//推导过程和一维整形数组相同
#include <stdio.h>
{
char s[] = "hello";
char *p = s;
while (*(p+i) != '\0')
{
putchar(*(p+i));
++i;
}
while (*p != '\0')
{
putchar(*p);
p++;
}
return 0;
}
int puts(const char *s);
形参设计 const char*,其目的是为防止函数中的误操作,对应的实参可以为数组名、指针变量或直接一个字符串常量。
优点:
(1)可以提前发现问题,将运行时的问题,提前的编译时
(2)const char*可以接收char* 也可以接收 const char*,提高参数的适应性
注意:能写成const的都写成const
const //只读
const int a; //a此时是一个只读的变量
const int*p= &a; //const 限定是基类型,表示不能通过*p的方式修改基类型
int const *p = &a;//与const int *p = &a等价
int * const p = &a;//const限定指针变量,表示将p限定为只读,表示p不能被修改
const int *p const p = &a;//基类型和指针变量都被限定为只读,均不能被修改,即p=&b和*p=b都是错误的
原则:就近原则 ,const离谁近就限定谁
应用:(1)如果不想通过*p方式改变基类型对应的数据,则int const *p = &a;或const int*p= &a;
(2)如果指针变量p定义好后,不想再指向别的变量,则int * const p = &a;
int a=10;
const int *p = &a;//*p为只读。a可读可写
int * const p = &a;
a = 20;//编译结果:a = 20 *p = 20
printf("a=%d\n",a);
printf("*p = %d\n",*p);
指针 +字符串
字符串:在C语言中是按照字符数组的形式存储
处理字符串:
char s[] = "hello";//表示在栈上开辟一块空间,用字符串常量中的"hello"进行初始化
const char *p = "hello";//表示p指向了字符串常量区中的"hello",只能做读取操作,不能被修改
char s [] = "hello" //字符串常量区的数据初始化了栈上的空间,
//相当于将字符串常量区的hello拷贝到了栈中,
//可以进行修改
const char *p = "hello"; //p指向字符串常量区的hello的地址,在更改*p时会报错
void Puts(const char *s)
{
while (*s != '\0')
{
putchar((*s)++);
}
}//编译之后会报错
三、指针高阶
1.指针+二维整型数组
(1)二维数组
C语言中不存在真正的二维数组,二维数组本质是一维数组的一维数组,二维数组也符合数组的特点(连续性、有序性、单一性)
(2)从二维数组的本质出发,二维数组可写成int[3] a[2];
&a[0]
//a[0]---int [3]
//&a[0]---int [3] *
//c语言中不支持int [3] *
//正确写法int(*)[3]--数组类型的指针---数组指针
int (*p)[3] = a;//p指向二维数组a
*p<=>a[0]//相当于是内部这个一维数组的数组名,逐层访问*(p+i)<=>int[3]*(*(p+i)+j)<=>a[i][j]最终访问到具体的元素
注意:
二维数组的操作从二维数组的本质进行的,二维数组的本质一维数组的一维数组,直接的访问操作,也是一维一维的展开
2.指针+二维字符型数组
char s[][10] = {"hello","world","china"};
char(*p)[10] = s;//p指向二维数组s
*(*(p+i)+j)//操作到具体的字符
3.指针的数组
char s[10 ]= "hello"//在栈中存放字符串数据
char *p = "hello"//存放在字符串常量区中,p的类型char *,char * 的指针变量p相当于代表一个字符串
char * pstr[3] = {"hello","world","hello"};数组中存放的是各个字符串的地址
指针数组:存放地址数据的数组即指针的数组
char **q = pstr; //二级指针
char *p[] = {s[0],s[1],s[2]};//p所指向的是每个字符串的地址而不是单个的字符
char* maxOfStr(char * *p,int len)//char**p为指向字符指针数组的指针p
{
//char max[10];
char *max = *p;//声明一个字符指针 max 并初始化为指向数组的第一个字符串
int i = 0;
for (i = 1; i < len; ++i)
{
if (strcmp(max,*(p+i)) < 0)//max为第一个字符串
{
max = *(p+i);
}
}
return max;
}
void reverseStr(char **begin, char **end)
{
while (begin < end)
{
char *t = *begin;
*begin = *end;
*end = t;
begin++;
end--;
}
}
void bubbleSort(char **p,int len)
{
int i = 0;
int j = 0;
for (i = len-1; i > 0; --i)
{
for (j = 0; j < i; ++j)
{
if (strcmp(*(p+j),*(p+j+1)) > 0)
{
char *t = *(p+j);
*(p+j) = *(p+j+1);
*(p+j+1) = t ;//t为字符串
}
}
}
}
int main(void)
{
const char *p[] = {"hello","world","china"};
bubbleSort(p,3);
puts("---------------");
printStr(p,3);
return 0;
}
#include <stdio.h>
//argc 命令行参数的个数
//argv 存放命令行参数的字符串的指针数组
int main(int argc, const char *argv[])
{
printf("argc = %d\n",argc);
int i = 0;
for (i = 0; i < argc;++i)
printf("argv[%d] = %s\n",i,argv[i]);
return 0;
}
4.指针+函数
通过指针的方式来调用函数
函数名---代表函数入口地址
int add(int a,int b)
该函数的函数名对应的数据类为int (int a,int b)----函数类型,代表一类函数,返回值为int,带有两个int型的形参变量
int (*p) (int,int) = add;//int(int,int) *p
说明:(1)可以定义一个函数类型的指针变量,来保存函数的入口地址
(2)有了这个指针变量,通过指针变量,进行函数调用
回调函数 callback---c语言中使用了函数指针实现
int func1(int n)
{
return n;
}
int func2(int n)
{
return n*n;
}
int func3(int n)
{
return n*n*n;
}
void choieSortN(int *a,int len,int(*p)(int))
{
int i = 0;
int j = 0;
for (i = 0; i < len-1; ++i)
{
for (j = i+1; j < len; ++j)
{
if (p(a[i]) > p(a[j])) //函数调用
{
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}
}
}
void printArray(int *a,int len)
{
int i = 0;
for (i = 0; i < len; ++i)
{
printf("%d ",a[i]);
}
putchar('\n');
}
int main(void)
{
int a[] = {1,8,-3,6,5,-4,2,7};
int len = sizeof(a)/sizeof(a[0]);
choieSortN(a,len,func2);
printArray(a,len);
return 0;
}