指针-进阶(C语言期末复习)

属于一些指针的进阶知识点。(建议有学过一点的初学者来看)

栈区、堆区

先用一串代码引入:

#include <stdio.h>
#define N 5
void *func(){
    int a = 10;
    int *pa_ = &a;
    return pa_;
}
void *func2(){
    int b = 20;
    int *pb_ = &b;
    return pb_;
}
int main()
{
    int *pa = func();
    printf("%d %p\n", *pa, pa);
    int *pb = func2();
    printf("%d %p\n", *pb, pb);
    printf("%d %p\n", *pa, pa);
} 

我们会发现pa的值发现改变为20(有些编译器输出的是0,问题不大)

这是为什么呢:

引入一个概念:在函数运行结束后,函数中定义的所有变量(栈区变量),都会被自动释放。

可以这样想,在调用不同函数时会从同一个特定地区开始取地址,在函数调用后这些地址上的值就会被释放没有,在这里只将地址传给了pa,pb,在调用 func2 之后还指向有效的内存空间,该内存空间中存储的值为 20。(这里知道栈区存在和基本的流程就行)

 这里的*pa,*pb叫悬挂指针

接下来:

堆区变量:使用malloc()函数分配的内存,
1.程序员需要手动释放。
2.如何释放?

  • 调用free()函数。
  • a.out
#include <stdio.h>
#include <stdlib.h>
#define N 5
void *func(){
	int *pa = (int*)malloc(sizeof(int));
	*pa = 10;
	return pa;
}
void *func2(){
	int *pb = (int*)malloc(sizeof(int));
	*pb = 20;
	return pb;
}
int main()
{
	int *pa = func();
	printf("pa:%d %p\n", *pa, pa);
	int *pb = func2();
	printf("pb:%d %p\n", *pb, pb);
	printf("pa:%d %p\n", *pa, pa);
	free(pa);
	free(pb);
} 

 (使用malloc函数要加头文件<stdlib.h>).
这里就会发现地址不同,值也不会随之改变。(这里了解"堆区里的值不会在函数结束后被自动释放"就行)

函数指针

函数指针可以和数组一样可以将命名作为名
例如一个函数func,则&func=func

#include <stdio.h>
int func(int a){
    int b = 10;
    int c = a*b;
    return c;
}
int main(){
    //定义一个指向函数的指针,这是一个指针
    int (*pa)(int) = NULL;
    /*
    int *pa(int);是一个返回值为int*的函数了
    所以得定义int (*pa)(int);
    (*pa)醒目得表示了这是一个指针
    (*pa)(int)表示了函数指针的类型
    最前面的int表示函数的返回值类型
    */
    pa = func;
    int res1 = func(10);
    int res2 = pa(10);
    printf("func(10): %d pa(10): %d\n", res1, res2);
    printf("func地址: %p pa地址: %p\n", &func, pa);

    return 0;
}

指向数组第一个元素的指针

#include <iostream>
using namespace std;
void test1(){
    int arr[] = {1,2,3};
    cout <<"arr:" << arr << endl;
    cout << "&arr[0]:" << &arr[0] << endl;
    cout << "&arr:" << &arr<< endl;
}
int main(){
    test1();
    return 0;
}

 这里arr的地址不能改变(比如arr+=1)
但一个指向arr或者其第一个元素地址的指针pa可以访问其后面的元素

#include <iostream>
using namespace std;
void test2(){
    int arr[] = {1,2,3};
    int *pa = &arr[0];
    cout <<"pa[0]:" << pa[0] << endl;
    cout << "pa[1]:" << pa[1] << endl;
    cout << "pa[2]:" << pa[2]<< endl;
    cout <<"*pa:" << *pa << endl;
    cout << "*(pa+1):" << *(pa+1) << endl;
    cout << "*(pa+2)" << *(pa+2)<< endl;
    //指向数组第一个元素的指针,可以被改变
    //并且,还可以像数组一样用[]来取元素
    //也可以像指针,用*来取元素的值
}
int main(){
    test2();
    return 0;
}

数组类型的指针

我们把数组当成一种数据类型,也可以定义一个指向数组的指针

#include <iostream>
using namespace std;
void test3(){
    int arr[] = {1,2,3};
    int (*parr)[] = &arr;
    /*
    加圆括号:因为[]的优先级高于*
    int*parr[]表示是一个指针数组,数组中的元素都是int*类型
    而int (*parr)[]会先表示是一个指针,然后表示是数组类型的指针。
    */
    cout <<"(*parr)[0]:"<<(*parr)[0] << endl;
    cout << "(*parr)[1]:"<<(*parr)[1] << endl;
    cout << "(*parr)[2]:"<<(*parr)[2] << endl;
    cout << "parr:"<<parr << endl;
    cout << "*parr:"<<*parr << endl;
}
int main(){
    test3();
    return 0;
}

这样(*parr)就能代替数组名,来使用[]访问元素

数组的元素全是指针

#include <iostream>
using namespace std;
void test4(){
    int a = 10, b = 20, c = 30;
    int *arr[] = {&a, &b, &c}; // 定义的是指针数组,数组类型是指针int*
    // arr[0] == &a;
    // *(&a) == a;
    // *(arr[0]) == a;
    cout << "*arr[0]:" << *arr[0] << endl;
    cout << "*arr[1]:" << *arr[1] << endl;
    cout << "*arr[2]:" << *arr[2] << endl;
}
int main(){
    test4();
    return 0;
}

 混合版本

#include <iostream>
using namespace std;
void test5(){
    // int* (*parr)[]
    /*
        分析:*parr 表示是一个指针
        (*parr)[] 表示是一个数组类型的指针
        int* 表示(*parr)[] 数组的每一个元素是int*类型
    */
   int a = 10, b = 20, c = 30;
   int* arr[] = {&a, &b, &c};
   int* (*parr)[] = &arr; // 定义的是指针数组,数组类型是指针int*
   /*
   这里arr前加&说明:
   因为 arr 的类型是 int* [](数组类型),而 parr 所期望的是一个指向 int* [] 类型数组的指针(也就是 int* (*)[] 类型),通过取 arr 的地址 &arr,就得到了符合 parr 期望类型的指针值,这样才能将 parr 正确地初始化指向 arr 这个数组。

   其次:&arr==arr虽然在数值上表现相同,但在类型上是有区别的:
   arr 的类型:arr 的类型是 int* [],它本质上是一个指针数组,在表达式中使用时,它会隐式地转换为指向其首元素的指针,也就是 int** 类型(指向 int* 类型的指针)。例如,当把 arr 传递给一个函数期望接收 int** 类型参数的函数时,这样的隐式转换就能正常进行。

    &arr 的类型:&arr 的类型是 int* (*)[],它是一个指向 int* 类型元素数组的指针,强调的是指向整个数组的概念,而不是指向数组中的某个元素。
   */
   // *parr == arr;
   // arr[0] == &a;
   // (*parr)[0] == &a;
   // *&a == a;
   cout << "*((*parr)[0]):" << *((*parr)[0]) << endl;
   cout << "*((*parr)[1]):" << *((*parr)[1]) << endl;
   cout << "*((*parr)[2]):" << *((*parr)[2]) << endl;
}
int main(){
    test5();
    return 0;
}

const 和指针

指向const变量的指针

必须有const修饰

#include <iostream>
using namespace std;
int main(){
    const int a = 10;
    const int *pa = &a;
    cout <<"a:" << *pa << endl;
    //const 修饰的变量,只能由指向const变量的指针取指向
    // const位置必须在*左边
    // 可以指向普通变量
    int b = 20;
    pa = &b;
    cout << "b:" << *pa << endl;

    return 0;
}

被const修饰的指针

#include <iostream>
using namespace std;
int main(){
    int a = 10;
    int *const pa = &a;//一旦指向了一个变量,不能再指向别的变量,但是被指向的变量是可以改变的
    cout << "a:" << *pa << endl;
    // int b = 20; 报错
    a = 20;
    cout << "a:" << *pa << endl;

    return 0;
}

结合

#include <iostream>
using namespace std;
int main(){
    int a = 10, b = 20;
    const int num = 10;
    const int *const pa = &num;
    cout << "num:" << *pa << endl;
    // pa和a都不能改变
    // pa = &b;  //报错
    // num = 20;  //报错

    return 0;
}

二级指针

首先来解决一个很常见的问题,大家都知道不行

#include <iostream>
using namespace std;
void swap(int a, int b){
    int temp = a;
    a = b;
    b = temp;
}
int main(){
    int a=10, b = 20;
    swap(a, b);
}

 在将a,b传入swap时,会再开辟跟他们一样空间、值,但地址不同的a',b',如下:

而在函数内交换,只会改变a',b'的值,不会改变a,b的值 

#include <iostream>
using namespace std;
void swap(int *pa_, int *pb_){
	cout << "函数里未交换前的地址:" << endl;
	cout << "pa':" << pa_ <<"  " <<"pb':" << pb_ << endl;
	int *temp = pa_;
	pa_ = pb_;
	pb_= temp;
	cout << "函数里交换后的地址:" << endl;
	cout << "pa':" << pa_ <<"  " <<"pb':" << pb_ << endl;
}
int main(){
	int a=10, b = 20;
	int *pa = &a;
	int *pb = &b;
	cout << "函数外未交换前的地址:" << endl;
	cout << "pa:" << pa <<"  " <<"pb:" << pb << endl;
	swap(pa, pb);//传入a,b的地址
	/*
	还是会给pa,pb分配一个副本
	*/
	cout << "函数外交换后的地址:" << endl;
	cout << "pa:" << pa <<"  " <<"pb:" << pb << endl;
}

 同样是函数里的副本pa_,pb_发生交换

 

但不会改变pa,pb的值

所以要使得交换的是指针,得传入一个指针得指针及二级指针
在传入时还是会开辟新的副本ppa, ppb 

#include <iostream>
using namespace std;
void swap(int** ppa, int** ppb){
	cout << "函数里未交换前的地址:" << endl;
	cout << "pa':" << *ppa <<"  " <<"pb':" << *ppb << endl;
	int *temp = *ppa;    // temp == pa
	*ppa = *ppb;         // pa = pb;
	*ppb = temp;         // pb = temp;
	//改变的是pa,pb的值 
	cout << "函数里交换后的地址:" << endl;
	cout << "pa':" << *ppa <<"  " <<"pb':" << *ppb << endl;
}
int main(){
	int a=10, b = 20;
	int *pa = &a;
	int *pb = &b;
	cout << "函数外未交换前的地址:" << endl;
	cout << "pa:" << pa <<"  " <<"pb:" << pb << endl;
	swap(&pa, &pb);
	cout << "函数外交换后的地址:" << endl;
	cout << "pa:" << pa <<"  " <<"pb:" << pb << endl;
}

总结:
如果要通过函数改变一个指针的值,要往函数中传入指针的指针
如果要通过函数改变一个变量的值,要往函数中传入这个变量的地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wirepuller_king

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值