C++笔记(三)【指针】
文章目录
一、基础知识
1.1 定义
指针:一个值为内存地址的变量(或数据对象)
内存中:
int 变量a(内存地址+内容)
int 指针变量b(内存地址+内容(变量a的地址))*
叫做b指向a
1.2 关于定义时int* p 和 int *p的写法
1.int* p的写法偏向于地址,即p就是一个地址变量
2.int *p的写法偏向于值,*p是一个整型变量
3.声明中的*号和使用在的*号含义完全不同
推荐:采用第一种方法用int*来声明一个整型指针变量
1.3 使用方式
int num=1000;
int* ptr_num=# //但不能直接给一个常量地址
*ptr_num=90;
//num=90
1.4 特殊注意点
#include <iostream>
using namespace std;
int main()
{
char ch='a';
char* ptr_ch=&ch;
cout << ptr_ch << "\t" << *ptr_ch << endl; //a a 会与想象的不同
//原因
char* str="唐柳健is"; //在c语言时代char*就默认后面跟的是字符串的地址,而不是字符的地址,因此上面打印的时候按照字符串的地址方式打印,肯定会出错
//解决方法强转成char型指针
cout << (char*)ptr_ch << endl; //此方法有时候编译器不让转
//改成这个方法
cout << (void*)ptr_ch << endl;
return 0;
}
二、 指针类型
2.1 空指针
一定要给指针初始值,要不然采用默认值很危险,为此可以用空指针,强烈建议初始化所有指针
int* kjk;//野指针
//下面三个等价
int* mine=nullptr;
int* mine=0;
int* mine=NULL; //要加上#include <cstdlib>
2.2 void* 指针
可以存放任意对象的地址
int nm = 10;
int* ptr_n = #
void* ptr_m = #
// ptr_m==ptr_n
注意点
1.无法修改指向的变量值,因为类型不清楚
如:
*ptr_m=90;
2.void指针类型一般用来:
拿来和别的指针比较(当不知道函数输出的指针属于什么类型的时候)
或者作为函数的输入输出
数组中也可以来存指针来节省空间大小
三、引用
3.1 什么是引用
核心:为了使得代码写的更漂亮方便
定义:为对象起了别名
int int_value=2022;
int& reValue=int_value;
//错误,引用必须被初始化
int& reVla2;
注意:
- 引用并非对象,只是给一个已经存在的对象起了别名
- 引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起,如
int& ref_value=10;
- 引用必须初始化,使用引用之前不需要测试其有效性,所以一定程度上比指针高效
指向常量的引用是非法的
可以改成
const double& ref=100;
3.2 引用与指针的关系
- 引用对指针进行了简单的封装,底层仍然为指针
- 获取引用地址时编译器会进行内部转换
如下,相当于把星号指针封装了:
int num=100;
int& rel_num=num;
rel_num=90;
cout << &num << "\t" << &rel_num << endl;
//实际上
int num=100;
int* rel_num=#
*rel_num=90;
cout << &num << "\t" << rel_num << endl;
四、指针与数组
4.1 联系
数组:
存储在一块连续的内存空间中
数组名就是这块连续内存空间的首地址
#include <iostream>
using namespace std;
int main()
{
double scores[]={2,5,65,7,2};
double* ptr=&scores[0];
cout << scores << "\t" << ptr << endl;
//scores=ptr
//指向数组的指针可以进行下标操作
double* pt=scores;
cout << pt[3] << endl;
return 0;
}
4.2 区别
但是指针与数组不是一码事!
#include <iostream>
using namespace std;
int main()
{
double scores[]={2,5,65,7,2};
double* pt=scores;
cout << sizeof(scores) << "\t" << sizeof(pt) << endl;
//40 8 40显然是8字节*5=40 相当于打回原形了虽然数组名指向的是首地址
return 0;
}
五、指针运算
理解:指针的平移
5.1 指针的递增递减
#include <iostream>
using namespace std;
int main()
{
double scores[]={2,5,65,7,2};
double* pt=scores;
for(int i=0;i<5;i++){
cout << *pt++ << endl; //平移的是一个double类型大小的字节,这里为8,即使sizeof(double)
}
return 0;
}
5.2 指针的直接加减
但是注意越界问题很危险
#include <iostream>
using namespace std;
int main()
{
double scores[]={2,5,65,7,2};
double* pt=scores;
pt+=2;
cout << *pt << endl;
pt-=2;
cout << *pt << endl;
cout << *(pt+3) << endl;
cout << *(pt+i) << endl;
return 0;
}
5.3 取数组的元素
第i个元素的地址:&num[i]
或 num+i
第i个元素的值:num[i]
或 *(num+i)
int* ptr=&num[4];
int* ptr=num+4;
//等价的
很重要的一点!!
数组名不能加加,只有自由的指针才可以
不管是指针,尽量不要贸然的移动指针,尽量少在自身上++,可以+i
double scores[]={2,5,65,7,2};
double* pt=scores;
cout << *++scores << endl; //会报错的,因为++就相当于scores=scores+1 改变了,*(scores+2)则没有改变scores本身所以是可以的
六、动态分配内存
1.使用new分配内存
- 指针真正用武之地:在运行阶段分配未命名的内存以存储值
- 在此情况下只能通过指针来访问内存
#include <iostream>
using namespace std;
int main()
{
//运行阶段为一个int值分配未命名的内存,使用指针来访问这个值
// ptr在栈区, 在堆区分配了一块int型空间
int* ptr=new int;
*ptr=90;
ptr++;//这个行为很危险,因为一旦++后上面的内存空间就变成内存泄漏,尽量避免这种操作
delete ptr; //一定要和new成对出现
//数组也可以这样
int* nums = new int[7];
int numss[7];
delete []nums;
//sizeof(nums) 得到结果4字节,运行时候才会动态给空间
//sizeof(numss) 得到28
return 0;
}
2.使用delete释放内存
不能释放声明变量分配的内存
另外,不要创建两个指向同一内存的指针,有可能误删两次
七、关于程序的内存分配
7.1 类型
1.栈区
由编译器自动分配释放,一般存放函数的参数值,局部变量的值等,操作方式类似数据结构中的栈,先进后出
2.堆区(heap)
一般由程序猿分配释放,若没释放,程序结束时可能由操作系统回收,注意与数据结构中的堆概念不同,分配方式类似链表
3.全局区(静态区-static)
全局变量和静态变量存储在一起,程序结束后由系统释放
4.文字常量区
常量字符串就放在这里,程序结束时由系统释放
5.程序代码区
存放函数体的二进制代码
7.2 示例
#include <iostream>
using namespace std;
int num1=0; //全局初始化区
int* ptr1; //全局未初始化区
int main()
{
//栈区
int num2;
char str[] = "tlj is coder";
char* ptr2;
//赋值的右边的字符串及数字都是在常量区的,左边在栈区
char* ptr3="tlj";
double nums[23]={2,3,4,5};
//全局(静态)初始化区
static int num3=1024;
//右边,即分配的内存在堆区,左边的指针本身在栈区
ptr1 = new int[10];
ptr2 = new char[30];
return 0;
}
八、指针与二维数组
直接看代码理解
#include <iostream>
using namespace std;
int main()
{
//二维数组的一维作为了行,这个指针相当于五个一维数组组成的
int (*p2)[3]= new int[5][3];
p2[2][3]=90;
for(int i=0;i<5;i++){
for(int j=0;j<3;j++){
cout << p2[i][j] << '\t';
cout << *(*(p2+i)+j) << ',';
}
cout << endl;
}
cout << "另一种" << endl;
int arrays[5][3] = {{1,2,3},{3,4,5},{7,8,9},{1,5,6},{4,7,8}};
int (*p1)[3] = arrays;
//第二行第一列的地址,可以与下面结合观察,他们每个行的地址是相邻的
cout << &p1[1][0] << endl;
for(int i=0;i<5;i++){
for(int j=0;j<3;j++){
//指的是每行数组的第一个元素的地址
cout << p1+i << '\t';
}
cout << endl;
}
return 0;
}
指针还能用于,如结构体中不能有函数,则可以用指针指向一个函数。这样结构体里就有方法了,这也是实现面向对象的一个方式。