1、指针——即地址 &num
一个变量的地址称为该变量的指针。通过变量的指针能够找到该变量。
2、指针变量——专门用于存储其他变量地址的变量
指针变量pnum的值就是变量num的地址,指针与指针变量的区别,就是变量值与变量的区别。
3、为表示指针变量和它指向的变量之间的关系,用指针运算符“*”表示。
int num = 10;
&num //num的地址或者叫num的指针
int* pnum = # //pnum是指针变量 这里存储num整形变量的指针
*pnum = 20; //通pnum访问指向的指针来访问对应变量的内存空间,使用*pnum就相当于取到pnum指向的变量。
#include
int main(int argc,char *argv[])
{
int a = 10, b = 20;
int *p;
p = &a;
printf("before a = %d\n", a);
*p = 15;
printf("after a = %d\n",a);
p = &b;
printf("before b = %d\n", b);
*p = 15;
printf("after b = %d\n", b);
return 0;
}
会手画变量内存分配的过程(内存分配的类型),以及指针变量的指向!
栈(局部变量 函数参数 由系统自己管理的 从定义这个局部变量的函数开始 到结束)
堆(动态内存分配 free释放)
静态全局区(初始化为非0的全局变量和静态变量 初始化为0的全局变量和静态变量 常量)
代码区(保存代码的常量区)
练习1:使用指针变量求解:输入两个整数,按升序(从小到大排序)输出。
#include
int main()
{
int a = 20, b = 10;
int *pa = &a, *pb = &b;
#ifdef METHOD_ONE
int temp = 0;
//方法一:交换数值
printf("Method One %d %d\n", *pa, *pb);
if ((*pa) > (*pb))
{
temp = *pa;
*pa = *pb;
*pb = temp;
}
#else
//方法二:交换地址
printf("Method Two %d %d \n", *pa, *pb);
int *ptmp;
if ((*pa) > (*pb))
{
ptmp = pa;
pa = pb;
pb = ptmp;
}
}
方法1:
内容上的交换(相当于把房间A的东西,全部交换到房间B中)
方法2:
指针的交换(打开房间A和打开房间B的钥匙进行了交换)
对于使用钥匙打开房间A和房间B的效果是一致的,但是内容的交换要比钥匙的交换开销要大得多!!!所以建议使用方法2
练习2:将练习1程序中方法1的代码,封装到函数中实现。(指针作为函数参数,可以使用指针改变指针指向对应变量的值)函数参数为什么声明为二级指针???
#include
void swap(int *pa, int *pb);
int main()
{
int a = 20, b = 10;
swap(&a,&b);
printf("a = %d,b = %d\n",a ,b);
return 0;
}
//这样声明函数交换地址失败
void swap(int *pa, int *pb)
{
int *temp = NULL;
temp = pa;
pa = pb;
pb = temp;
}
swap(a,b); //直接传值进去,交换不了数值
原因:函数swap中交换的是自己参数pa和pb的值,并不会影响main函数中pa和pb所指向的地址。
如果想交换pa和pb指向的地址。则需要传入main函数中pa和pb的地址。
因此函数的参数应该声明为指向pa和pb指针的指针变量(二级指针)即
void swap(int **pa, int **pb);
声明 int *pt[]
使用指针进行数组元素以及地址的访问
方法一:数组下标法
#define M1
//#define M2
//#define M3
int main(int argc,char *argv[])
{
int array[10];
printf("Please input 10 integer:");
#ifndef M1
for(int i = 0; i < (sizeof(array) / sizeof(int)); i++)
{
scanf("%d",&array[i]);
}
#elif M2
for(int i = 0; i < (sizeof(array) / sizeof(int)); i++)
{
scanf("%d",array + i);
}
#elif M3
#endif
#ifndef M1
for(int i = 0; i < (sizeof(array) / sizeof(int)); i++)
{
printf("%d, ",array[i]);
}
printf("\n");
#elif M2
for(int i = 0; i < (sizeof(array) / sizeof(int)); i++)
{
printf("%d, ",*(array + i));
}
printf("\n");
}
声明 int (*pt)[]
void printfArray(int* pArray, int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
printf("%s",*(pArray + i));
}
printf("\n");
}
void printfNames(char* *pNames, int size)
{
int i = 0;
for (i = 0; i < size; i++)
{
printf("%s",*(pNames + i));
}
printf("\n");
}
int main(int argc, char* argv[])
{
int array[10] = {1,2,3};
int* pArray = array; //pArray就是数组指针 指向array
char* names[3] = {"111","222","333"}; //names是一个数组 这个数组保存char*类型的指针 names为指针数组
}
对没有指向的指针变量进行操作,是很危险的事情。我们对没有指向的指针变量叫做野指针。
野指针会造成非法访问内存。经常会造成程序崩溃或者意想不到的错误。
所以在定义指针变量的时候,如果暂时没有明确的指向,对其一般初始化为NULL(空指针)
int * parray = NULL;
说明:
1、指针变量的值是可以改变的,所以必须注意其当前值,否则容易出错;
2、指向数组的指针变量,可以指向数组以后的内存单元,虽然没有实际意义。
3、对指向数组的指针变量(px和py)进行算术运算和关系运算的含义。
①可以进行的算术运算,只有以下几种:px±n,px++/++px,px–/–px,px-py
px±n:将指针从当前位置向前(+n)或回退(-n)n个数据单位,而不是n个字节。显然,px++/++px和px–/–px是px±n的特例(n=1)。px-py:两指针之间的数据个数,而不是指针的地址之差。
int *px = &a[3];
int *py = &a[1];
**px-py 为 2(代表px和py之间间隔了几个int类型的数据个数!)
px+py没有意义(两个指针变量或者指针相加是没有意义,加完的那个数据不知道指向何处)
int *p;指针变量定义的时候要么指向明确的变量的地址,要么初始化为空
如果不初始化,p由可能指向任何地方,这种指针称之为野指针,通过野指针去改变指向的不确定的地址的行为是很危险的。
野指针:动态分配内存,释放之后,没有清空,这种也是野指针。
**
表示两个指针所指地址之间、位置前后关系:前者为小,后者为大。
例如,如果指针px所指地址在指针py所指地址之前,则px(py的值为1。)
if (px < py) //真 px在py之前
{
}
else //假px在py之后或者一致
{
}
指向二维数组的指针变量的定义
#include
int main(int argc, char *argv[])
{
int array[3][4] = {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12}
};
int (*parray)[4] = array;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
printf("%d ", array[i][j]);
}
}
return 0;
}
int(*parray)[4] = array;
parray是一个指针变量,指向一个int[4]类型的数组首地址。
int parray[4] = array; //错误 这个parray它是一个数组 数组的每个元素都是int 类型的
在程序运行过程中,数组的大小是不能改变的,这种数组称为静态数组。静态数组的缺点是:对于事先无法准确估计数据量的情况,无法做既满足处理需要,又不浪费内存空间。
所谓的动态数组是指,在程序运行过程中,根据实际需要指定数组的大小。
在C语言中,可利用内存的申请和释放库函数,以及指向数组的指针变量可当数组名使用的特点,来实现动态数组。
编码规范:
不可以返回局部变量的地址!
动态数组计算n(由用户从键盘输入的)名学生的平均成绩示例:
#include
#include
int main(int argc, char *argv[])
{
int stucount = 0;
float* pscore = NULL;
int i = 0;
float sum = 0, ave = 0;
printf("请输入有多少学生:");
scanf("%d", &stucount);
pscore = (float*)malloc(sizeof(float)*stucount);
//如果指针为空,直接返回
if(pscore == NULL)
{
return 0;
}
printf("请输入%d个学生的成绩\n", stucount);
for (i = 0; i < stucount; i++)
{
scanf("%f", pscore + i); //scanf("%f", &pscore[i]);
}
for (i = 0; i < stucount; i++)
{
sum += *(pscore + i); //sum += pscore[i];
}
ave = sum / stucount;
printf("ave score is %.1f\n", ave);
free(pscore);
pscore = NULL;
return 0;
}
编码规范:
1、malloc动态申请内存,不一定100%成功,对于申请的返回地址需要判断是不是NULL< /font>,如果是NULL说明申请失败,则进行返回或者异常处理;
2、动态申请的内存是内存模型中的堆区申请的< /font>,生命周期是由程序员控制的,需要调用free才能够释放;
3、free之后,还需要对之前使用的指针变量置NULL,避免野指针
字符串在内存中的起始地址称为字符串的指针,可以定义一个字符指针变量指向一个字符串。
练习1:字符数组与指向字符串的指针变量
#include
int main(int argc, char *argv[])
{
int i = 0;
char str1[32] = "I love Dalian"; //字符数组
char *pstr = "I love Dalian!!!"; //指向字符串的指针
str1[13] = "?";
//遍历字符数组
for (i = 0; str1[i] != '\0'; i++)
{
printf("%c", str1[i]);
}
printf("\n");
//*(pstr + 13) = "?" 错误 此时pstr指向的是常量区,所以不能改变
//使用指针变量遍历字符串
for (;*pstr != '\0';pstr++)
{
printf("%c", *pstr);
}
printf("\n");
#if 1
//pstr是指针变量,可以改变其指向
pstr = str1;
*(pstr + 13) = '.'; //OK 此时的pstr指向的str1,是分配在栈空间的数组变量,所以可以用*(pstr+n)的方式修改
//使用指针变量遍历字符串
for(; *pstr != '\0'; pstr++)
{
printf("%c", *pstr);
}
printf("\n");
#endif
//str1是数组名,是地址,是常量,不可以改变
//str1 = pstr; 错误
//str++ 错误
return 0;
}
练习2:实现自己的字符串copy函数
#include
void mystrcpy(char* dest, char* src);
int main(int argc, char *argv[])
{
char str1[20] = "I Love Dalian!!!";
char str2[20];
mystrcpy(str2,str1);
printf("str2 = %s\n", str2);
return 0;
}
void mystrcpy(char* dest, char* src)
{
int i = 0;
for(i = 0; *(src + i ) != '\0'; i++)
{
*(dest + i) = *(src + i);
}
*(dest + i) = '\0';
}
练习3:字符串copy函数的改善 const
#include
//const char* src 表示的是 src指向的内容是作为常量来处理,因此,不可以使用src改变src所指向的内容
void mystrcpy(char* dest, const char* src);
int main(int argc, char *argv[])
{
char str1[20] = "I Love u";
char str2[20];
mystrcpy(str2,str1);
printf("str2 = %s", str2);
return 0;
}
void mystrcpy(char *dest, const char* src)
{
int i = 0;
for (i = 0; *(src + i) != '\0'; i++)
{
*(dest + i) = *(src + i);
}
*(dest + i) = '\0';
}
int a = 100;
const int *pa1 = &a; //const 与pa1和int比 离int最近,修饰的是int 代表pa1指向的数据不可以通过pa1改变
int const *pa2 = &a; //const 与pa2和int比 离int最近,修饰的是int 代表pa2指向的数据不可以通过pa2改变
int *const pa3 = &a; //const 与pa3和int比 pa3更近 修饰的是pa3 代表pa3不可以改变,但是pa3的内容是可以改变的
const int* const pa4 = &a; //int和pa4都使用const修饰了,两者都不可以改变
函数指针的概念:函数的入口地址
一个函数在编译时,被分配了一个入口地址,这个地址就称为该函数的指针。
可以用一个指针变量指向一个函数,然后通过该指针变量调用此函数。
//函数指针变量padd调用add函数
#include
int add(int a, int b)
{
return a + b;
}
int add1(int a )
{
return a;
}
int main(int argc, char *argv[])
{
//函数名调用add函数
int c = add(10,20);
printf("c = %d\n",c);
//函数指针变量padd调用add函数
int (*padd)(int a, int b) = add;
c= (*padd)(30,40);
printf("c = %d\n", c);
//padd = add1
/*
NG padd的类型是指向 int(*)(int int)的函数指针变量
如果将add1赋值给padd则编译器会报错,不能将int(*)(int)赋值给int(*)(int, int)
原因就是类型不匹配
*/
return 0;
}
int* test(void)
{
int* p = NULL;
p = (int*)malloc(sizeof(int)); //malloc是从堆中申请的,如果不free一直可用
if(NULL == P)
{
return NULL;
}
return p;
}
int main(int argc, char* argv[])
{
int *pa = NULL;
int* (*ptest)(void) = test; //ptest指向了test函数入口
pa = (*ptest)(); //通过函数指针ptest调用test函数
*pa = 200;
free(pa);
pa = NULL;
return 0;
}
普通方法的实现
#include
void stepone(char* pname)
{
printf("Puts %s to Bingxiang\n", pname);
if(NULL == pname)
{
return;
}
printf("Bingxiang Men Kaile!!!\n");
}
void steptwo(char* pname)
{
if(NULL == pname)
{
return;
}
printf("%s jin Bingxiang le!!!\n", pname);
}
void stepthree(char* pname)
{
if(NULL == pname)
{
return;
}
printf("Bingxiang Men guanshang!!!\n", pname);
}
int main(int argc, char *argv[])
{
stepone("DaXiang");
steptwo("DaXiang");
stepthree("DaXiang");
return 0;
}
函数指针方法实现
顺序化调用,能够实现按步骤调用。
好处:利于维护和扩展
#include
void stepone(const char* pname)
{
printf("Puts %s to Bingxiang\n", pname);
if(NULL == pname)
{
return;
}
printf("Bingxiang Men Kaile!!!\n");
}
void steptwo(const char* pname)
{
if(NULL == pname)
{
return;
}
printf("%s jin Bingxiang le!!!\n", pname);
}
void stepthree(const char* pname)
{
if(NULL == pname)
{
return;
}
printf("Bingxiang Men guanshang!!!\n", pname);
}
typedef struct {
void (*pFunc)(const char* name);
}Step_st;
Step_st stepTable[3] = {
{stepone},
{steptwo},
{stepthree},
};
void PutBingXiangEngine(const char* name)
{
for (int i = 0; i < sizeof(stepTable)/sizeof(Step_st); i++)
{
(*stepTable[i].pFunc)(name);
}
}
int main(int argc, char *argv[])
{
PutBingXiangEngine("大象");
return 0;
}