指针基本概念及其使用技巧

指针基本概念及其常见用途

指针基本概念

指针含义

指针是C语言中的一个概念,其本质上是一个内存地址。指针占据的内存大小由其所在的计算机架构决定,64位机中指针大小为8个字节,32位机中指针大小为4个字节

在64位机中测试如下代码,会输出8

int main(int argc, char* argv[]){
    int* p;
    printf("%d",sizeof(p));//output 8
}

指针本身只是一个内存地址,内存地址是没有意义的,有意义的是该地址中指向的值,我们最常用的利用指针访问其指向对象的方法是解引用符号*

int a = 8;
p = &a;  //&符号为取变量的地址,使得指针p的值为变量a所在的地址
printf("%d",*p);//output 8

指针运算

指针是可以进行指针运算的,但是指针运算的规则比较特殊,它与它指向的类型有关

//指针运算
long* lp;//lp指向long类型
printf("%ld\n",sizeof(long));//8
printf("%p\n",lp);
printf("%p\n",lp+1);
printf("%ld\n",(long)(lp+1)-(long)lp);//8
char* cp;//cp指向char类型
printf("%ld\n",sizeof(char));//1
printf("%p\n",cp);
printf("%p\n",cp+1);
printf("%ld\n",(long)(cp+1)-(long)cp);//1
char** pp;//pp指向char*类型
printf("%ld\n",sizeof(char*));//8
printf("%p\n",pp);
printf("%p\n",pp+1);
printf("%ld\n",(long)(pp+1)-(long)pp);//8
char (*ap)[10];//ap指向char[10]类型
printf("%ld\n",sizeof(char[10]));//10
printf("%p\n",ap);
printf("%p\n",ap+1);
printf("%ld\n",(long)(ap+1)-(long)ap);//10

从上面的例子可以看出,指向每次加一,实际上加的值为其指向的类型占据的内存大小,这样的设计导致指针运算会非常方便

指针常见用途

字符串

字符串是c语言中是非常常用的,其一般有3种表示方式

char* s = "hello world";
char s1[] = {'h','e','l','l','o',' ','w','o','r','l','d'};
char s2[] = "hello world";

试着输出这三种字符串的值

printf("%ld\n",strlen(s));//11
printf("%ld\n",strlen(s1));//22
printf("%ld\n",strlen(s2));//11

会很惊奇的发现,第二个字符串的值为22,这是因为,C语言中的字符串实际上是一系列连续的字符,在C语言中使用字符’\0’作为字符串的结束位。C语言中的函数一般都是通过检测’\0’这个标志位来判断字符串的一些属性的,比如strlen

size_t strlen (const char *__s){
	size_t count = 0;
	while(*s != '\0'){
		count++;
		s++;
	}
	return count;
}

char s2[] = “hello world”;这个语句实际上是先把常量字符串"hello world"转化为了{‘h’,‘e’,‘l’,‘l’,‘o’,’ ‘,‘w’,‘o’,‘r’,‘l’,‘d’,’\0’},然后再将该字符数组赋值给了s2,而s1中字符数组是不存在结束标识’\0’的,由于两个变量s1,s2是连续声明并定义的,此时内存布局如下

请添加图片描述

s1到结束标志符’\0’,一共间隔了22个字符,故长度为22

数组与指针

数组和指针是比较容易混淆的两个概念,原因如下

1.数组可以当成指针使用

int arr[] = {1,2,3,4,5};
printf("%d\n",*arr);//1
printf("%d\n",*(arr+1));//2

2.数组作为函数参数时会退化为指针,此时数组与指针没有差别,所以即使visit1和visit2形参一个为数组,一个为指针,但最终都为指针

int arr[] = {1,2,3,4,5};
printf("%ld\n",sizeof(arr));//20
visit1(arr,2);
visit2(arr,3);
int visit1(int arr[],int i){
	printf("%ld\n",sizeof(arr));//8
    printf("%d",arr[i]);//3
    return 0;
}
int visit2(int* arr,int i){
	printf("%ld\n",sizeof(arr));//8
    printf("%d",arr[i]);//4
    return 0;
}
//output 20 8 3 8 4

3.数组名的值本身相当于第一个元素的首地址

int arr[] = {1,2,3,4,5};
printf("%d\n",*arr);//1

4.字符指针和字符数组都可以直接用常量字符串定义,且用法差别不大

char* s = "hello world";
char s2[] = "hello world";

但是数组与指针还是有不同之处的

1.对于sizeof运算符,数组的大小为其数组中存储的变量个数与变量类型的乘积,是变化的,与具体数组中元素个数有关,指针的大小为机器字的大小,是固定不动的,这表明数组本质上是指针上加了一层封装(多了数组的大小信息)

2.在函数中声明数组,是声明在栈空间的,也就是说,函数退出之后数组即会被销毁,而如果利用指针指向一个malloc申请的堆内存空间,即使函数退出来,该空间也不会销毁。

int* p1 = test1();
printf("%d",*p1);//output:Segmentation fault
int* test1(){
    int arr[] = {1,2,3};
    return arr;
}
int* p2 = test2(); 
printf("%d",*p2);//output:1
int* test2(){
    int* arr = (int*)malloc(sizeof(int)*3);
    arr[0] = 1;arr[1] = 2;arr[2] = 3;
    return arr;
}

函数指针

函数指针是C语言一个非常强大的功能,他能用来实现回调函数,也能用来实现面向对象机制

直接看代码

#include<iostream>
using namespace std;
typedef void(*pball)(int);//typedef定义一个函数指针类型pball
void baseball(int x){
    cout<<"baseball "<<x<<endl;
}
void basketball(int x){
    cout<<"basketball "<<x<<endl;
}
void ball_callback(int x, pball pball){
    cout<<"callback"<<endl;
    pball(x);
}
int main(){
    ball_callback(3,baseball);
    ball_callback(5,basketball);
}
//output
/*
callback
baseball3
callback
basketball5
*/
#include<iostream>
using namespace std;
//利用函数指针实现面向对象,vfs相当于面向对象中的基类,fs2相当于基类的子类
struct vfs{
    void vfswrite(void (*write)(int)){
        cout<<"vfs:";
        write(3);
    };
    void vfsread(int (*read)()){
        cout<<"vfs:";
        int r = read();
        cout<<"read"<<r<<endl;
    };
};
void fs1write(int i){
    cout<<"fs1:";
    cout<<i<<endl;
};
int fs1read(){
    return 1;
};
struct fs2{
    static void fs2write(int i){
        cout<<"fs2:";
        cout<<i<<endl;
    };
    static int fs2read(){
        return 2;
    };
};
int main(){
    struct vfs v;
    //struct fs1 f1;
    struct fs2 f2;
    v.vfswrite(fs1write);
    v.vfsread(fs1read);
	v.vfswrite(f2.fs2write);
    v.vfsread(f2.fs2read);
}     
//output
/*
vfs:fs1:3
vfs:read1
vfs:fs2:3
vfs:read2
*/

指针数组与数组指针

在C语言中,由于访问下标符号[]优先级比解引用符号*高,而C语言中声明变量时会以优先级最高的符号先结合以确定变量类型,故指针数组和数组指针分别以如下形式声明

int (*PointerToArrar)[10];//这是一个指针,指向int[10]类型的数组
printf("%ld\n",sizeof(PointerToArrar));//利用sizeof证明其为一个指针 
printf("%ld\n",(long)(PointerToArrar+1)-(long)PointerToArrar);//利用指针运算来证明其元素为int[10] 
int* ArrayOfPointer[10];//这是一个数组,数组中元素为int*类型的指针
printf("%ld\n",sizeof(ArrayOfPointer));//利用sizeof证明其为一个数组
printf("%ld\n",(long)(ArrayOfPointer[0]+1)-(long)ArrayOfPointer[0]);//利用指针运算来证明其元素为int* 
//output:8 40 80 4

结构体指针

结构体是C语言中一种自定义类型,当我们想把结构体传递进函数中时,一般会用到结构体指针,下面是一些简单的结构体指针的用法

struct student;
struct  students
{
    int size;
    int capacity;
    student* stu[5];
    void push(student* c){
        stu[size++] = c;
    };
    student* pop(){
        return stu[--size];
    };
};
struct student{
    char* name;
    int grade;
    int cla;
};
students* stus = (students*)malloc(sizeof(students));
student* stu1 = (student*)malloc(sizeof(student));
student* stu2 = (student*)malloc(sizeof(student));
stu1->name = "lihua";
stu1->grade = 100;
stu1->cla = 3;
stu2->name = "小明";
stu2->grade = 97;
stu2->cla = 2;
stus->capacity = 5;
stus->size = 0;
stus->push(stu1);
stus->push(stu2);
printf("%s",stu1->name); //lihua
printf("%s",stus->pop()->name); //小明

综合案例

下面是两个比较有意思的指针综合案例,相信可以让你对指针有不一样的理解

#include<stdio.h>
//验证ptr-1的值
int main(){
    int a[] = {1,2,3,4,5};
    int* ptr = (int*)((&a)+1);
    int* ptr2 = (int*)(a+1);
    printf("%d %d %d", *(ptr-1), *(ptr2-1), *(a+1));//output 5 1 2
    return 0;
}
//output : 5 1 2
include<iostream>
using namespace std;

int main(int arg1, char** arg2){
    //./test 12 345 678 
    int test = 0, i;
    for( i=1; i < arg1; i++){
        test = test * 10 + (*arg2[i] - '0');
        //*arg2[i]为第一个字符
    }
    cout<<test<<endl;
}
//output 136

总结

指针是C语言中一个威力很大的特性,利用指针及指针运算,再加上C语言对程序员极弱的限制,我们可以轻松操纵指针变量周围的内存,做到一些很酷的事。

举个例子,比如malloc函数会返回的一个指向内存区域首地址的指针,该内存区域前方是一些此内存区域的一些信息(比如内存区域大小),如果我们利用指针修改了这些信息,比如将内存区域大小修改为了0,那么即使我们调用了free,这块内存区域依旧得不到释放,这就造成了内存泄露。

指针威力这么大,是一把双刃剑,容易害人也容易害己,因此C++中加入了引用特性,相当于指针上做了一层封装,对指针的行为做了一些限制,使得我们只能操作变量对应的内存而不能操纵变量周围的内存,让程序世界变得安全起来。

因此,即使我们有了很多使用指针的经验,对于写程序而言,最好的实践依旧是:优先使用引用而非指针!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值