属于一些指针的进阶知识点。(建议有学过一点的初学者来看)
栈区、堆区
先用一串代码引入:
#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 = #
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;
}
总结:
如果要通过函数改变一个指针的值,要往函数中传入指针的指针
如果要通过函数改变一个变量的值,要往函数中传入这个变量的地址