一、基础知识
1、声明指针:typeName * pointName;
- 一般初始化(3种):
int a=10;
int *p;
p=&a;
- 也可以在声明中初始化
int a=5;
int *pt=&a;
*运算符两边的空格是可以选的,传统C程序员使用这种格式:
int *p 这强调的是*p是一个int类型的值。但很多C++程序员使用这种格式:
int* p,这强调的是:int*是一种类型---指向int的指针,在哪里添加空格没有任何区别,甚至可以这样做int*p;
但是要知道下面的声明是创建一个指针P1和一个int变量p2
int* p1,p2;
今天,在练习C++书上的习题时,发现一个问题,原题如下:C++允许按值传递结构,也允许传递结构的地址。如果glitz是一个结构变量,如何按值传递它?如何传递它的地址?这两种方法有何利弊?
我们在定义子函数的形参时,为什么调用时用到的参数与定义时用到的形参不同? 我想可以用上面的论述解释。【int* p,这强调的是:int*是一种类型---指向int的指针,在哪里添加空格没有任何区别,甚至可以这样做int*p;】
- 用new运算符初始化:typeName * pointer_name=new typename;
int * pn=new int;
new int 告诉程序寻找一块int类型的地址,用来存储数据(类似于int a=10;)。
要注意一点:new分配的内存位于堆(heap)或自由存储区(free store)中,而常规变量分配的内存是在栈(stack)中。堆和栈的区别在于:栈(stack)由编译器自动分配释放,而堆(heap)一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。
同时在使用new时,需要用delete来释放内存。方法十分简单,在delete后加指向待释放内存的指针即可,比如:
int* p = new int;
delete p;
这样就可以释放掉通过new为开辟的内存了。
需要注意的是,delete只能释放由new开辟的内存。注意以下三种情况:
1、如果试图释放常规变量声明分配的内存,会报错。如:
//delete不能释放常规变量声明分配的内存
int i =3;
int* p = &i;
2、如果试图释放已被delete释放的内存,会报错。如:
//delete释放后,p指针不会被删除,而是重新指向了一处新的地址。
int* p = new int;
delete p;
delete p;
3、delete可以释放空指针,这是安全的,不报错。如:
int* p = NULL;
delete p;
delete p;
#include<iostream>
using namespace std;
int main()
{
int a=10;
int *p1;
p1=&a;
int b=1;
int *p2=&b;
int *p3=new int;
*p3=1001;
cout<<p1<<" "<<*p1<<endl;
cout<<p2<<" "<<*p2<<endl;
cout<<p3<<" "<<*p3<<endl;
return 0;
}
- a与*p1的值一样,&a与p1的值一样。
- 运行结果:程序运行了两次,每次输出的结果不一样,原因很简单,程序每次运行的时候,向系统申请内存,系统随机分配内存,就像您去宾馆开房,如果您不提前预约指定房号,每次得到的房间编号大概率不会相同。
二、指针的应用场景
1、值传递
所谓值传递,就是函数调用时实参将数值传入形参,值传递时,如果形参发生改变,并不会影响实参。
#include <iostream>
using namespace std;
void swap(int num1, int num2)
{
cout << "交换前:" << endl;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
int temp = num1;
num1 = num2;
num2 = temp;
cout << "交换后:" << endl;
cout << "num1 = " << num1 << endl;
cout << "num2 = " << num2 << endl;
//return ; 当函数声明时候,不需要返回值,可以不写return
}
int main()
{
int a = 10;
int b = 20;
swap(a, b);
cout << "mian中的 a = " << a << endl;
cout << "mian中的 b = " << b << endl;
// system("pause");
return 0;
}
2、地址传递:利用指针作函数参数,可以修改实参的值。
#include <iostream>
using namespace std;
//值传递
void swap1(int a ,int b)
{
int temp = a;
a = b;
b = temp;
}
//地址传递
void swap2(int * p1, int *p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main()
{
int a = 10;
int b = 20;
swap1(a, b);// 值传递不会改变实参
swap2(&a, &b); //地址传递会改变实参
cout << "a = " << a << endl;
cout << "b = " << b << endl;
// system("pause");
return 0;
}
三、空指针(NULL指针)
本质上,定义指针时,要进行初始化,如果没有确定的值,就让它指向NULL。
#include <iostream>
using namespace std;
int main()
{
//指针变量p指向内存地址编号为0的空间
int * p = NULL; //访问空指针报错
//内存编号0 ~255为系统占用内存,不允许用户访问
cout << *p << endl;
return 0;
}
四、野指针
定义:指针指向的内存已经释放,但指针的值不会清零,对野指针的操作将不可预知
#include <iostream>
using namespace std;
int main()
{
//指针变量p指向内存地址编号为0x1100的空间
int * p = (int *)0x1100; //访问野指针报错
cout << *p << endl;
return 0;
}
五、函数指针
每一个函数都有一个入口地址,函数指针是指向函数入口地址的指针变量,有了指向函数的指针变量后,就可以利用函数指针变量来调用函数。
作用:调用函数和做函数的参数
声明格式:类型说明符 (*函数名) (参数)
int (*fun)(int x,int y);
函数指针是需要把一个函数的地址赋值给它,有两种写法:
fun = &Function;
fun = Function;
取地址运算符&不是必需的,因为一个函数标识符就表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。
调用函数指针有两种方法
x=(*fun)();
x=fun();
两种方式均可,其中第二种看上去和普通的函数调用没啥区别,如果可以的话,建议使用第一种,因为可以清楚的指明这是通过指针的方式来调用函数。当然,也要看个人习惯,如果理解其定义,随便怎么用都行啦。
#include <iostream>
using namespace std;
int add(int x,int y)
{
return x+y;
}
int sub(int x,int y)
{
return x-y;
}
//函数指针
int (*fun)(int x,int y);
int main()
{
//第一种写法
fun = add;
cout<< "(*fun)(1,2) = " << (*fun)(1,2)<<" "<<fun(1,2)<<endl;
//第二种写法
fun = ⊂
cout<< "(*fun)(5,3) = " << (*fun)(5,3)<<" " << fun(5,3);
return 0;
}
六、指针函数
简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。返回值为地址的函数即为指针函数.
声明格式为:*类型标识符 函数名(参数表)
看看下面这个函数声明:
int fun(int x,int y);
这种函数应该都很熟悉,其实就是一个函数,然后返回值是一个 int 类型,是一个数值。
接着看下面这个函数声明:
int *fun(int x,int y);
这和上面那个函数唯一的区别就是在函数名前面多了一个*号,而这个函数就是一个指针函数。其返回值是一个 int 类型的指针,是一个地址。
指针函数的写法:
int *fun(int x,int y);
int * fun(int x,int y);
int* fun(int x,int y);
这个写法看个人习惯,其实如果*靠近返回值类型的话可能更容易理解其定义。
示例:
#include <iostream>
using namespace std;
typedef struct _Data{
int a;
int b;
}Data;
//指针函数
Data* f(int a,int b)
{
Data * data = new Data;
data->a = b;
data->b = a;
return data;
}
int main()
{
//调用指针函数
Data * myData = f(4,5);
cout<< "f(4,5) = " << myData->a<<" " << myData->b;
return 0;
}
注意:在调用指针函数时,需要一个同类型的指针来接收其函数的返回值。
参考资料:函数指针和指针函数用法和区别_luoyayun361的博客-CSDN博客_指针函数
七、指针数组与数组指针
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
下面到底哪个是数组指针,哪个是指针数组呢:
A)
int *p1[10];
B)
int (*p2)[10];
每次上课问这个问题,总有弄不清楚的。这里需要明白一个符号之间的优先级问题。
“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。我们可以借助下面的图加深理解:
使用new来创建动态数组
1、 语法:typeName pointer=new type[arraySize];
new 运算符返回第一个元素的地址,并将该地址赋给指针;
2、当使用完new分配的内存块时,应使用delete来释放他们。
语法:delete []pointer;
3、用指针访问数组中的其他元素时,只要把指针当作数组名使用即可。
#include<iostream>
using namespace std;
int main()
{
double* p = new double[3];
p[0] = 0.2;
p[1] = 0.5;
p[2] = 0.6;
cout << "p[1] is " << p[1] << ".\n";
p = p + 1;
cout << "Now p[0] is " << p[0] << "and" << "p[1] is " << p[1] << ".\n";
p = p - 1;
delete[] p;
return 0;
}
数组名与指针
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
using namespace std;
double wages[3] = { 1000.0,2000.0,3000.0 };
short stack[3] = { 3,2,1 };
double* pw = wages; //C++将数组名解释为地址
short* ps = &stack[0];
cout << "pw=" << pw << ", *pw= " << *pw << endl;
cout << "ps=" << ps << ", *ps= " << *ps << endl;
pw = pw + 1;
ps = ps + 1;
cout << "指针pw和ps加1" << endl;
cout << "pw=" << pw << ", *pw= " << *pw << endl;
cout << "ps=" << ps << ", *ps= " << *ps << endl;
}
1、C++将数组名解释为地址,且此地址为第一个数组元素的地址
wages=&wages[0]=address of first element of array
在初始化指针时我们令
double *pw=wages;
因为此时指针与数组的地址值相同,我们可以把指针当成数组
2、将指针变量加1后,其增加的值等于指向的类型占用的字节数
由上述程序可知,当pw增加1时,其地址增加8;ps增加1时,其地址增加2;
3、*(stacks+1)等价于stacks[1]
4、指针是变量,而数组名是常量;
八、其他
在学习C++的过程中我们经常会用到.和::和:和->,在此整理一下这些常用符号的区别。
1、A.B则A为对象或者结构体;
2、A->B则A为指针,->是成员提取,A->B是提取A中的成员B,A只能是指向类、结构、联合的指针;
3、::是作用域运算符,A::B表示作用域A中的名称B,A可以是名字空间、类、结构;
4、:一般用来表示继承;
初始化:包括声明与赋值-----调用