C语言-指针

本文详细介绍了C语言中的指针概念,包括如何通过指针访问变量、指针的分类、指针偏移量以及在数组和函数中的应用。通过实例展示了指针在内存管理、变量交换和数组操作中的重要作用,并探讨了使用指针优化程序性能的原因。同时,提到了无类型指针`malloc`函数用于动态内存分配,并警告了内存泄漏的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引入地址概念

int a = 10;

C语言中通过变量名a来访问变量的值10,就是说变量名a是一个地址,地址上放了值10

变量可以通过变量名访问,也可以通过存放10的地址,即变量的地址来访问10这个值

变量名访问

int a = 10;
int b = 9;

printf("a=%d\n",a);
printf("b=%d\n",b);

打印变量地址

int a = 10;
int b = 9;

printf("a地址=%p\n",&a);
printf("b地址=%p\n",&b);
// & 取地址运算符,取变量名所代表的变量的内存地址

通过地址取得变量的值

int a = 10;
int b = 9;

printf("通过地址取值a=%d\n",*(&a));
printf("通过地址取值b=%d\n",*(&b));
//此时 * 代表的是一个运算符,跟 + - * / 运算符类似,该运算符的功能是:取出内存地址中数据的值(取内容)

指针就是操控地址的方式

指针变量引入

int a = 10;                  //整型变量    存放的是整数
char c = 'c';                //字符变量    存放的是字符
int array[3] = {1,2,3};      //数组       存放的是一串数据

a、c、array都是变量
不同是变量类型不同
a=整数
c=字符
array=数组

指针变量存放的是地址

定义指针变量

int a = 10;

int *p;                      //指针变量    存放的是地址
int* p;

p = &a;                      //给指针变量赋值

int *p2 = &a;                //定义并初始化一个指针变量

&a(0x660cffC0); 取地址

*p = &a;

*p取了地址a的值10,而&a是取的是变量a的地址,=是把a的地址赋值给10,所以此种写法错误

这是错误的写法,只有在定义一个指针变量的时候,才是指针的标识符,其它情况,都是一个运算符,取地址中的内容

指针变量是存放指针的变量
指针变量是存放地址的变量

通过指针来访问变量

#include <stdio.h>

int main()
{
    int a = 10;
	int b;
    b = 9;
    
    int *p1;
    p1 = &a;
    
    int *p2 = &b;

	printf("a=%d\n",a);
	printf("b=%d\n",b);

	printf("通过指针获取a地址=%p\n",p1);
	printf("通过指针获取b地址=%p\n",p2);
    
    printf("通过指针取值a=%d\n",*p1);
    //此时 * 代表的是一个运算符,跟 + - * / 运算符类似,该运算符的功能是:取出内存地址中数据的值(取内容)
    //只有在定义一个指针变量的时候,才是指针的标识符,其它情况,都是一个运算符,取地址中的内容
	printf("通过指针取值b=%d\n",*p2);
	

指针的分类

int *p1;     //整型的指针只能指向整数的地址
char *p2;    //字符型的指针只能指向字符型的地址
#include <stdio.h>

int main()
{
	int *p1;
    char *p2;
    
    int a = 10;
	char c = 'A';
	
    p1 = &a;
    p2 = &c;
    
	printf("a=%d\n",*p1);
    printf("c=%c\n",*p2);

    system("pause");
    return 0;
}

指针偏移量

int *p;
整型数指针位置移动一下是4个字节,且指针的移动地址是连续的

char *p;
字符型指针位置移动一下是1个字节,且指针的移动地址是连续的

#include <stdio.h>

int main()
{
	int *p1;
    char *p2;
    
    int a = 10;
	char c = 'A';
	
    p1 = &a;
    p2 = &c;
    
	printf("a=%d\n",*p1);
    printf("c=%c\n",*p2);
    
    //指针++
    printf("a的地址=%p\n",p1);
    printf("a++的地址=%p\n",++p1);
    printf("c的地址=%p\n",p2);
    printf("c++的地址=%p\n",++p2);
    

    system("pause");
    return 0;
}

执行结果:

a=10
c=A
a的地址=0060FF04
a++的地址=0060FF08
c的地址=0060FF03
c++的地址=0060FF04

int array[3] = {1,2,3};
整型数组每个元素大小是4个字节,且地址是连续的

打印数组的地址

#include <stdio.h>

int main()
{
	int array[3] = {1,2,3};
    
    printf("array[0]的地址=%p\n",&array[0]);
    printf("array[1]的地址=%p\n",&array[1]);
    printf("array[3]的地址=%p\n",&array[2]);
    
	int *parray;
    parray = &array[0];
    
    printf("数组array[0]指针的地址=%p\n",parray++);
    printf("数组array[1]指针的地址=%p\n",parray++);
    printf("数组array[2]指针的地址=%p\n",parray);

    system("pause");
    return 0;
}

执行结果:

array[0]的地址=0060FEF0
array[1]的地址=0060FEF4
array[3]的地址=0060FEF8
数组array[0]指针的地址=0060FEF0
数组array[1]指针的地址=0060FEF4
数组array[2]指针的地址=0060FEF8

历遍获取数组的值

#include <stdio.h>

int main()
{
	int array[3] = {1,2,3};
	int i;
    for(i=0;i<3;i++){
		printf("array[%d]=%d\n",i+1,array[i]);
    }
    
	int *parray;
    parray = &array[0];
    //parray = array;
    
    for(i=0;i<3;i++){
		printf("指针array[%d]=%d\n",i+1,*parray++);
		//printf("指针array[%d]=%d\n",i+1,*parray[i]);
    }

    system("pause");
    return 0;
}

执行结果:

array[1]=1
array[2]=2
array[3]=3
指针array[1]=1
指针array[2]=2
指针array[3]=3

数组赋值给指针:

int array[3] = {1,2,3};
int *parray;

parray = &array[0];

parray = array;

指针取值:

*parray

*parray[i]

加入指针优化成绩录入程序:

#include <stdio.h>

int initPeople(int n)
{
	printf("需要录入几个学生分数:");
    scanf("%d",&n);
    return n;
}

void initScores(int *array,int n)
{
	int i;
	for(i=0;i<n;i++){
		printf("请输入第%d个学生的分数:",i+1);
        scanf("%d",array);
        if(*array > 100 || *array < 0){
			printf("非法分数!\n强制退出!\n");
            system("pause");
            exit(-1);
        }
        array++;
	}
}

void scoresPrint(int *array,int n)
{
	int i;
    for(i=0;i<n;i++){
		printf("第%d个学生的分数是:%d\n",i+1,*array++);
	}
}

int getMax(int *array,int n)
{
	int i;
    int max;
    max = *array;
    for(i=0;i<n;i++){
		if(max < *array){
			max = *array;
        }
        array++;
	}
    return max;
}

int getMin(int *array,int n)
{
	int i;
    int min;
    min = *array;
    for(i=0;i<n;i++){
		if(min > *array){
			min = *array;
        }
        array++;
	}
    return min;
}

float getAverage(int *array,int n)
{
	int i;
    int total = 0;
    float average;
    for(i=0;i<n;i++){
		total += *array;
		array++;
    }
    average = (float)total/n;
    return average;
}

void printRet(int a, int b, float f)
{
	printf("最高分:%d\n最低分:%d\n平均分:%f\n",a,b,f);
}

int main()
{
	int len = 0;
	int scores[len];
    int max;
    int min;
    float average;
    
    len = initPeople(len);
    
    int *p;
    p = scores;//可以通过数组名把数组的第一个元素的地址赋值给指针变量
    p = &scores[0];//可以通过数组首地址赋值给指针变量
    
    initScores(&scores[0], len);
    //函数调用过程中,传参就是一个赋值的过程。实际参数的值,给形式参数
    //函数调用可以传数组第一个元素的地址
    //函数传参就是把变量的地址到函数,让函数基于地址来控制,运算地址上的值
    
    scoresPrint(p, len);//函数可以通过传指针变量名给函数形参
    
    max = getMax(scores, len);
    
    min = getMin(scores, len);
    
    average = getAverage(scores, len);
    
    printRet(max, min, average);
    
    system("pause");
    return 0;
}

指针初略总结

1、变量访问的两种方式

  • 变量名
  • 指针(地址)

2、指针

  • 指向谁(注意类型) p = &a;
  • 偏移后指向谁 p++
    • int *p; p++偏移了4个字节
    • char *p; p++偏移了1个字节
#include <stdio.h>

int main()
{
	int a;
    a = 10;
    
    char c;
    c = 'A';
    
    int *p1;
    p1 = &a;
    
    char *p2;
    p2 = &c;
    
    printf("变量名a=%d\n",a);
    printf("地址a=%d\n",*(&a));
    printf("指针a=%d\n",*p1);
    
    printf("指针c=%c\n",*p2);
    
    int array[3] = {1,2,3};
    int *p3;
    p3 = array;
    
    int i;
    printf("====================数组下标访问数组====================\n");
    for(i=0;i<3;i++){
		printf("array[%d]=%d\n",i,array[i]);
    }
    printf("====================指针访问数组====================\n");
    for(i=0;i<3;i++){
		printf("array[%d]=%d\n",i,p3[i]);
		//通过下标方式访问数组指针的值
    }
    for(i=0;i<3;i++){
		printf("array[%d]=%d\n",i,*p3);
        p3++;
    }
    
    system("pause");
    return 0;
}

使用指针的理由

int a;
一般变量a的地址是系统随机分配的

C语言是可以让a的地址指定为自己想要的地址

指针可以强制某个地址存放想要的值

#include <stdio.h>

int main()
{
	int a;  //a的地址是系统随机分配的
    a = 10;
    
    int *p;
    p = &a;
    
    printf("a address is %p\n",p);
	
    int *p2 = (int *)0x0060ff00;   
    //因为指针变量是存放地址的变量,所以可以写一个地址,强转为整型,在把地址给到指针变量
    *p2 = 20;
    printf("在内存%p位置,存放的值是%d\n",p2,*p2);
    
    volatile int *p3 = (volatile int *)0x0060ff05;//类似修饰符
    
    system("pause");
    return 0;
}

volatile关键字1

作用:

1、确保本条指令不会因编译器的优化而省略

2、要求每次直接读值。

在多线程编程中,会因为线程在编程过程中对内存变量的值做了修改,在复制一份给寄存器, 而多线程的话,在另一个线程会对使用这个变量的值上存在一定的时效性,会对运行结果存在误差。

所以volatile关键字就是程序禁止去用寄存器上的变量的值,每次运行都是去调用内存上的值

有时编译器会对指定的值做优化修改,所以有这个关键是就的避免编译器优化

指针对地址的控制变化下,变量的值的变化:

对变量的值做交换

#include <stdio.h>

int main()
{
	int a = 10;
	int b = 5;
    
    int tmp;
    
    tmp = a;
    a = b;
    b = tmp;
    
    printf("a=%d\nb=%d\n",a,b);
    
    system("pause");
    return 0;
}

执行结果:
产生了交换

a=5
b=10

用函数做变量值的交换

#include <stdio.h>

void swap(int a, int b)
{
    int tmp;
    
	tmp = a;
    a = b;
    b = tmp;
}

int main()
{
	int a = 10;
	int b = 5;
    
    swap(a,b);
    
    printf("a=%d\nb=%d\n",a,b);
    
    system("pause");
    return 0;
}

执行结果:
没有产生交换

a=10
b=5

当传地址给到函数交换

#include <stdio.h>

void swap(int *a, int *b)
{
    int tmp;
    
	tmp = *a;
    *a = *b;
    *b = tmp;
}

int main()
{
	int a = 10;
	int b = 5;
    
    swap(&a,&b);
    
    printf("a=%d\nb=%d\n",a,b);
    
    system("pause");
    return 0;
}

执行结果:
产生了交换

a=5
b=10

指针是存放地址的变量

指针可以通过地址来控制变量名地址里的值

修改变量的值,一般通过是函数传参,是把变量的值在内存中复制一份给到函数形参,在由函数执行方法运算,处理,后面再把处理的结果通过不同的数据类型区分,返回出去,给到调用函数程序,最后释放掉函数

#include <stdio.h>

void jia(int a)
{
	a = a + 1;
    printf("jia:a=%d\n",a);
}

int main()
{
	int a = 10;

	jia(a);
    
    printf("a=%d\n",a);
    
    system("pause");
    return 0;
}

执行结果:

jia:a=11
a=10

通过指针修改变量的值,是通过把变量的地址给到处理函数,这样函数控制修改的是变量地址上的值,函数结束释放后可以不用返回修改后的数据值,而调用函数程序里变量的值已经发生了改变。

因为函数直接修改的是调用函数程序里变量的地址上面的值,而不是复制一份值给函数自己,再在内存中生成一份数据,在做修改

#include <stdio.h>

void jia(int *a)
{
	*a = *a + 1;
    printf("jia:a=%d\n",*a);
}

int main()
{
	int a = 10;

	jia(&a);
    
    printf("a=%d\n",a);
    
    system("pause");
    return 0;
}

执行结果:

jia:a=11
a=11

指针数组

1、很多指针,用数值的方式,放在一起

2、它是一个数组,数组中每一个元素都是指针

  • 好多变量的地址放一起的集合
#include <stdio.h>

int main()
{
	int a = 3;
    int b = 8;
    int c = 5;//三个毫无关系的整型变量
    
    int array[3]; //多个整数,叫做整数数组
    
    int* p;
    //定义指针数组
    int* parray[3];  
    //多个指针,叫做指针数组,数组中的每个元素都是一个指针变量
    //指针变量是存放地址的变量
    
    parray[0] = &a;
    parray[1] = &b;
    parray[2] = &c;
    //三个普通没有任何关系的整型变量的地址存入指针数组
    
    int i;
    for(i=0;i<3;i++){
		printf(" %d ",*(parray[i]));
    }
    putchar('\n');

    system("pause");
    return 0;
}

数组指针

1、一个指针变量

2、真正指向某个类型数组的指针

3、一般指针是指向数组的首地址,即数组的第一个元素地址。&array[0]

4、明确规定了指针要指向数组的类型及大小

整型数指针指向数组

#include <stdio.h>

int main()
{
	int a[3] = {1,2,3};
    
    int *p;
    //此指针不是数组指针,仅仅是一个普通的整型数的指针,只不过指针刚好指向了数组的首元素地址   
    p = a;
    //类于如此
    int b;
    int *p1;
    p1 = &b; 
    
    int i;
    for(i=0;i<3;i++){
		printf(" %d ",*p++);
    }
    
    system("pause");
    return 0;
}

  • int (*p)[3];

  • 数组的指针的定义方式

  • p = a;

  • 这是数组指针的赋值方式

  • p = &a;

  • 这是错误的复制赋值方式

数组指针指向数组

#include <stdio.h>

int main()
{
	int a[3] = {1,2,3};
    
    int (*p)[3];
    //数组的指针的定义方式
    //数组的指针强调的是类型,数组的个数,偏移值是偏移了整个数组的大小
    p = a; 
    
    int *p2;
    p2 = a;
    
    printf("数组a的地址是%p\n",a);
    printf("数组a的地址是%p\n",&a[0]);
    printf("p数组的地址是%p\n",p);
    printf("p2数组的地址是%p\n",p2);   //0060FEEC
    
    printf("=====================区别======================\n");
    printf("p++的结果是:%p\n",++p);     //0060FEF8 - 0060FEEC = 12
    printf("p2++的结果是:%p\n",++p2);    //0060FEF0 - 0060FEEC = 4
    

    system("pause");
    return 0;
}

执行结果:

数组a的地址是0060FEEC
数组a的地址是0060FEEC
p数组的地址是0060FEEC
p2数组的地址是0060FEEC
=====================区别======================
p++的结果是:0060FEF8
p2++的结果是:0060FEF0

数组指针不同定义方式的差别

会因为偏移量的不同,有不同的定义方式

int array[3] = {1,2,3};

整型数指针指向数组:

定义方式

  • int *p;

赋值方式

  • p = array;
  • p = &array[0];

取值方式

  • *p
  • p[0]

偏移值是移动一个数组元素的大小

int array[3] = {1,2,3};

数组指针指向数组:

定义方式

  • int (*p)[3];

赋值方式

  • p = array;

取值方式

  • (*p)[i]

偏移值是移动整个数组的大小

数组指针的使用

#include <stdio.h>

int main()
{
	int a[3] = {1,2,3};
    
    int (*p)[3];
    p = a;

    int i;
    for(i=0;i<3;i++){
		printf(" %d ",(*p)[i]);
    }
    
    system("pause");
    return 0;
}

函数指针

1、一个指针变量

  • void (*p)();
  • int (*a)(int a,int b);

2、存放的是函数的地址

  • p = printWelcome;

定义方式:

  • void (*p)();
  • 表示指针:*
  • 表示函数:()
  • 函数指针是专用的,格式要求很强(参数类型,个数,返回值),就像数组指针一样

函数指针赋值:

  • p = printWelcome;
  • 函数名就是地址,就像数组一样,数组名就是地址

通过指针调用函数:

  • p();
  • 直接通过指针名字+()
  • (*p)();
  • 通过取内容:*指针名()

示例代码

#include <stdio.h>

void printWelcome()
{
	printf("hello welcome to you!\n");
}

int main()
{
	int a = 10;
    printf("a=%d\n",a); //通过变量名来访问一个变量
    
    int *p = &a;
    printf("a=%d\n",*p); //通过指针来访问一个变量
    
    printWelcome();//通过函数名来调用函数
    
    //定义函数指针
    void (*p2)();
    //表示指针:*
    //表示函数:()
    //函数指针是专用的,格式要求很强(参数类型,个数,返回值),就像数组指针一样
    p2 = printWelcome;
    //函数名就是地址,就像数组一样,数组名就是地址
    
    //通过函数指针调用函数
    p2();//直接通过指针名字+()
    (*p2)();//取内容:*指针名()
    
    system("pause");
    return 0;
}

整型带参数函数指针

#include <stdio.h>
int add(int a,int b)
{
	return a+b;
}
int main()
{
    int (*padd)(int a,int b);
    padd = add;
    int ret = padd(1,2);
    //int ret = (*padd)(1,2);
    printf("ret=%d\n",ret);
    system("pause");
    return 0;
}

无类型指针malloc

void *malloc(size_t size);

开辟连续内存地址空间函数

void free(void *ptr);

释放掉开辟的地址空间

malloc函数用法

#include <stdio.h>

int main()
{
    //int a[3];
    int *a = (int *)malloc(3*sizeof(int)); //也是数组
    
    int i;
    for(i=0;i<3;i++){
		a[i] = i+1;
    }
    for(i=0;i<3;i++){
		printf("%d ",a[i]);
    }
    system("pause");
    return 0;
}

函数原型:
void *malloc(size_t size);

指针用法:
int *a = (int *)malloc(3 * sizeof(int));

  • size_t int
    • 需要开辟空间的大小
    • 只能为正整数
    • 开辟的空间是无类型的空间
  • (int *)
    • 把开辟的无类型的地址空间强转为整数类型
  • int *a
    • 把开辟的地址空间赋值给到整型指针变量a
    • 类似于数组赋值给到整型指针

优化示例

#include <stdio.h>
int main()
{
	int n;
    printf("请输入录入成绩的个数:\n");
    scanf("%d",&n);
    //int a[n];
    int *a = (int *)malloc(n*sizeof(int)); //也是数组
    int i;
    for(i=0;i<n;i++){
		printf("请输入第%d个学生成绩:\n",(i+1));
        scanf("%d",&a[i]);
    }
    for(i=0;i<n;i++){
		printf("第%d学生成绩为:%d\n",(i+1),a[i]);
    }
    free(a);
    a = NULL;
    system("pause");
    return 0;
}

内存泄漏

现象:
程序刚跑起来,很好,跑几个小时,或者几天,或者几周,程序崩溃

while(1){
	sleep(1);
	int *p = malloc(1024); 
}

malloc 申请空间,程序不会主动释放,linux中会在程序结束后,系统内存管理会回收这个空间

避免:

  • 注意循环中有没有一直申请内存空间
  • 及时合理的释放

释放:
free§;
p = NULL;

指针总结

定义整型变量
int a;

定义p为指向整型数据的指针变量
int *p;

定义整型数组a,它有5个元素
int array[5];

定义指针数组p,它由4个指向整型数据的指针元素组成
int *p[4]; //p++是偏移一个元素

p为指向包含4个元素的一维数组的指针变量
int (*p)[4]; //p++是偏移整个数组

f为返回整型数值的函数
int f();

p为返回一个指针的函数,该指针指向整型数据
int* p();

p为指向函数的指针,该函数返回一个整型值
int (*p)();

p是一个指针变量,它指向一个指向整型数据的指针变量
int **p;

p是一个指针变量,基类型为void(空类型),不指向具体的对象
void *p = NULL;


  1. C语言中volatile ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值