指针和指针变量

1、指针与指针变量

1、指针——即地址 &num
一个变量的地址称为该变量的指针。通过变量的指针能够找到该变量。
2、指针变量——专门用于存储其他变量地址的变量
指针变量pnum的值就是变量num的地址,指针与指针变量的区别,就是变量值与变量的区别。
3、为表示指针变量和它指向的变量之间的关系,用指针运算符“*”表示。

int num = 10;
&num  //num的地址或者叫num的指针
int* pnum = #	//pnum是指针变量  这里存储num整形变量的指针
*pnum = 20;		//通pnum访问指向的指针来访问对应变量的内存空间,使用*pnum就相当于取到pnum指向的变量。
#include <stdio.h>

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 <stdio.h>

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 <stdio.h>

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);

2、数组指针与指针数组

2.1、数组指针

声明 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");
}

2.2、指针数组

声明 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为指针数组
}

2.3、野指针

对没有指向的指针变量进行操作,是很危险的事情。我们对没有指向的指针变量叫做野指针。
野指针会造成非法访问内存。经常会造成程序崩溃或者意想不到的错误。
所以在定义指针变量的时候,如果暂时没有明确的指向,对其一般初始化为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由可能指向任何地方,这种指针称之为野指针,通过野指针去改变指向的不确定的地址的行为是很危险的。
野指针:动态分配内存,释放之后,没有清空,这种也是野指针。
**

2.4、指针的关系运算

表示两个指针所指地址之间、位置前后关系:前者为小,后者为大。
例如,如果指针px所指地址在指针py所指地址之前,则px(py的值为1。)
if (px < py) //真 px在py之前
{

}
else //假px在py之后或者一致
{
}

2.5、二维数组的访问方法

指向二维数组的指针变量的定义

#include <stdio.h>

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 类型的

2.6、动态数组的实现

在程序运行过程中,数组的大小是不能改变的,这种数组称为静态数组。静态数组的缺点是:对于事先无法准确估计数据量的情况,无法做既满足处理需要,又不浪费内存空间。
所谓的动态数组是指,在程序运行过程中,根据实际需要指定数组的大小。
在C语言中,可利用内存的申请和释放库函数,以及指向数组的指针变量可当数组名使用的特点,来实现动态数组。
编码规范:
不可以返回局部变量的地址!

动态数组计算n(由用户从键盘输入的)名学生的平均成绩示例:

#include <stdio.h>
#include <stdlib.h>

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;

}

2.7、动态申请内存使用注意点

编码规范:
1、malloc动态申请内存,不一定100%成功,对于申请的返回地址需要判断是不是NULL< /font>,如果是NULL说明申请失败,则进行返回或者异常处理;
2、动态申请的内存是内存模型中的堆区申请的< /font>,生命周期是由程序员控制的,需要调用free才能够释放;
3、free之后,还需要对之前使用的指针变量置NULL,避免野指针</ font>

3、字符串指针

字符串在内存中的起始地址称为字符串的指针,可以定义一个字符指针变量指向一个字符串。

练习1:字符数组与指向字符串的指针变量

#include <stdio.h>

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 <stdio.h>

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 <stdio.h>

//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';
}

4、const修饰指针总结

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修饰了,两者都不可以改变

5、函数指针

函数指针的概念:函数的入口地址
一个函数在编译时,被分配了一个入口地址,这个地址就称为该函数的指针。
可以用一个指针变量指向一个函数,然后通过该指针变量调用此函数。

//函数指针变量padd调用add函数

#include <stdio.h>

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 <stdio.h>

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;
}

函数指针方法实现
顺序化调用,能够实现按步骤调用。
好处:利于维护和扩展</ font>

#include <stdio.h>

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;
}

6、函数指针实现回调机制(callback)

  • 12
    点赞
  • 69
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值