*
指针运算符 可作为左值。表示查询到指针所对应的内存空间这样的操作。
&
地址运算符,可以概括为 取址运算符,从变量或对象等获取到该元素所在的内存空间中对应的地址。
指针定义
int i = 0;
int * pt = &i;
/*
未定义类型指针
void类型指针可以存入任何类型的变量地址,但是不能直接被使用。使用的时候需要强制转换类型。
*/
int i = 10;
bool b = false;
void * tentativePointer;
tentativePointer = & i;
i += static_cast<int *>(tentativePointer);
tentativePointer = & b;
b = !static_cast<bool *>(tentativePointer);
// 常量指针 指针所对应的地址的值被保护
int a;
a = 10; // √ a是变量 可以修改
const int *p1 = &a; // 指针
*p1 = 5; // × 不能通过p1 给a赋值
int b = 5;
p1 = &b; // √ 可以将p1转向其他变量
// 常指针 指针的地址被保护,即确定地址之后 不能修改,但对应的值可以修改。
int a;
a = 10;
int * const p2 = &a;
*p2 = 5; // √
int b = 5;
p2 = &b; // ×
指向对象的指针
指向对象的指针和其他类型的区别在于,访问对象的属性或方法不能通过.
操作符。需要使用->
。
实际上这里的object->method()
等价于 (* object).method()
,这是c++提供的一种语法糖。
另外,每个对象的方法内,默认隐含了一个this属性,实际上是指向该对象本身的。
指针的运算
算数运算
对指针的运算并非对地址进行修改,而是对于指针所指向的内存空间进行偏移定位。
而每一次移动的单位,取决于指针所表示的类型,例如 char 占用一个字节,那么 p++则会从010A0000
前往010A0001
,而如果是 int 类型,那么每次会移动4个字节,如从010A00B0
前往010A00B4
。
由于数组在内存中是紧密相连排列的,所以我们也就可以通过第一个元素的地址和[n]下标来查询对应的元素。
int a[] = {1,2,3,4,5};
cout << *(a+3) << endl;
// 会输出4
// *(a+3) 等价于 a[ 0 + 3 ]
关系运算
一般来说同类型的指针可以进行比较操作。
另外可以将指针与0做比较,判断指针是否为空。(如果是新标准 可能不行)
指针传参
指针传参是十分重要的一个特性了,失去了指针,C++也就失去了他最大的性能优势。
传递指针本身是很容易的,即使用 * type param_name
这样的形式定义参数即可。外部调用时,将对应的实参地址进行填入即可。
这时,如果为了保护数据的可靠性,可以用const修饰参数类型。
普通参数
// 批量打印
void printArray( const int * arr, int len ){
for( int i=0; i<len; i++ ){
cout << arr[i] << endl;
}
}
int a[] = {1,2,3,4,5};
printArray( a,5 );
// 批量修改
void batchIncrease( int * arr, int len, int n ){
for( int i=0; i<len; i++ ){
arr[i] += n;
}
}
int b[] = {1,2,3,4,5};
batchIncrease( b, 5, 2 );
printArray( b );
// 输出 3,4,5,6,7
当实参不是数组类型的时候,我们无法通过[]操作符进行寻秩操作,这个时候需要使用 * 运算符来获取地址对应的值。
void splitFloat(float x, int *intPart, float *fracPart) {
*intPart = static_cast<int>(x); //取x的整数部分
*fracPart = x - *intPart; //取x的小数部分
}
函数参数
需要实现传递函数作为回调函数的时候,我们可以将函数名作为 函数指针参数传递进去。比较典型的用法是,遍历回调。
例如我们对一系列的对象进行遍历的时候,我们设计的遍历函数是一个通用 或者说一个接口,它能够支持调用者用各式各样的方式来处理遍历时的元素,那么这个时候函数指针是非常有用的。
函数指针参数的格式为:return_type( * function_name )( function_params )
template <typename T>
void forEach( T * elements, int len , void(* callback)( const T el ) ){
for( int i=0; i<len; i++ ){
callback( T )
}
}
// 可以再考虑一下传递的T 采用引用的类型如何编写
除此之外,函数指针不仅限于传参,和普通类型一样,函数指针一样可以先定义,后赋值为各个具体的函数。
void (*pf)(int,char*);
void fun(int n,char *s) {......}
pf=fun;
指针类型函数
指针类型函数就是返回一个指针(内存地址)的函数。定义十分简单,在返回类型后增加 * 标识符即可。
但是需要注意,返回的指针应当是一个返回后依然有效的指针,否则会产生越界,野指针或是更多错误。
这个问题很好理解,如果你在网上购物,给了一个地址,千万不要给酒店门牌号,因为快递送过来的时候,你已经不在酒店了。无论是租房还是买房,只要你收货的时候,你这个地址还是有效的,那就可以~
所以无论是返回外部变量中的有效地址,还是通过new 进行动态分配的空间地址,都是可以顺利返回给调用者。
而动态分配的地址,永恒的点就是不要忘了delete。
其他补充
基于范围循环
for( type & e : array ){}
基于范围循环是类似于很多其他语言中提供的in
循环,比如Javascript中的for( var k in arr ){}
。
最近回复