C++快速讲解(四):指针
前言:主要介绍指针的使用,动态内存的使用,数组和指针的区别,指针的运算以及指针与常量,指针与函数,二级指针,函数指针,引用的介绍。
1.指针使用
1.1 初始化指针
#include <iostream>
#include <string>
using namespace std;
int main() {
//初始化指针
int age = 10;
int *p = &age;//p是一个指针,指向的是一个int类型的数据,这个数据是3
string name = "zhangsan";
string *pstr;//声明一个指针
pstr = &name;//指向一个string类型的数据,这个数据是zhangsan
//空指针声明.
int *p1 = nullptr;//推荐使用这种方法
int *p2 = NULL;
int *p3 = 0 ;
return 0;
}
- 空指针注意:指针在声明的时候必须初始化,指针声明然后没有初始化,是很危险的.因为它会指向随机的区域,如果未来使用这个指针修改了指向区域的数据。
1.2 指针地址和大小
#include <iostream>
using namespace std;
int main() {
//指针大小和大小
int age = 88;
int *p = &age;
cout << "指针的地址是: " << &p <<endl;
cout << "指针存储的是: " << p <<endl;
cout << "指针大小是: " << sizeof p <<endl;
cout << "age的大小是: " << sizeof age <<endl;
return 0;
}
1.3 指针的解引用
#include <iostream>
using namespace std;
int main() {
//指针解引用
int age = 88;
int *p = &age;
//通过指针,获取到指向位置的数据 打印88
cout << "*p:" << *p <<endl;
//使用指针修改原来的age
*p = 100;
//使用指针和变量的方式打印age
cout << "*p:" << *p <<endl;
cout << "age: " << age <<endl;
return 0;
}
2. 动态内存
2.1 申请和释放内存
#include <iostream>
using namespace std;
/*
* 申请内存和释放内存
*/
int main() {
//定义一个int类型的指针,并没有指向任何地方。
int * int_ptr = nullptr;
//在堆中申请内存,使用指针指向
int_ptr = new int;
//由于未赋值,所以输出的可能是未知的值
cout << *int_ptr << endl;
//修改开辟空间的数据值为100
*int_ptr = 100;
//解引用,输出100
cout << *int_ptr << endl;
delete int_ptr; //释放内存,因为在堆中申请的内存,不会随着程序执行结束,内存会自动回收,必须进行手动释放内存才可以,
return 0 ;
}
2.2 数组操作
#include <iostream>
using namespace std;
/*
* 使用new int[] 来给数组申请动态内存 , 然后使用 delete[] 释放申请的内存
*/
int main() {
//开辟了堆内存
int *scores = new int[10]{55,66,77,88,99,1,2,3,4,5};
cout<<"使用指针打印scores"<<endl;
for (int i = 0;i<10;++i){
cout<<*(scores+i)<<endl;
}
for (int i = 0; i < 10; ++i) {
scores[i] = 90 +i;
}
cout<<"使用指针打印修改后的scores"<<endl;
for (int i = 0;i<10;++i){
cout<<*(scores+i)<<endl;
}
//只有在删除申请的数组内存,才需要跟上 [] delete[]xxx
delete []scores;
return 0 ;
}
2.3 栈和堆的使用
栈和堆的使用注意:定义的变量存储的位置位于栈内存中,栈内存的数据,当函数执行结束后即会被释放。堆内存中的容量相比栈内存要大多了,但是堆内存并不提供回收释放的工作,允许程序申请内存空间,但是同时也要自己负责内存空间的释放工作。
3.数组和指针
#include <iostream>
using namespace std;
/*
* 数组与指针
*/
int main() {
int age []{18,9,20,33,55,66};
cout << "直接打印age:" <<age << endl; //实际上是打印数组第一个元素的地址
cout << "打印*age:" << *age << endl;//取到的是第一个元素 :18
int *age_ptr{age};//声明指针,存放的是数组第一个元素的地址
cout << "打印age_ptr:" << age_ptr << endl;
cout <<"解引用:" <<*age_ptr << endl;
return 0 ;
}
4.指针运算
4.1 指针运算
#include <iostream>
using namespace std;
/*
* 指针运算
*/
int main() {
int age []{18,9,20,33,55,66};
int *age_ptr{age};//声明指针,存放的是数组第一个元素的地址
//使用数组的手法打印数组
cout << age_ptr[0] << endl;
cout << age_ptr[1] << endl;
cout << age_ptr[2] << endl;
cout << age_ptr[3] << endl;
cout << age_ptr[4] << endl;
cout << age_ptr[5] << endl;
//对指针进行加法运算。由于score_ptr 是int类型,
//而int类型占用4个字节,所以每次相加打印出来的地址都会变长4个字节
cout << age_ptr << endl;
cout << (age_ptr + 1) << endl;
cout << (age_ptr + 2) << endl;
cout << (age_ptr + 3) << endl;
cout << (age_ptr + 4) << endl;
cout << (age_ptr + 5) << endl;
//指针解引用取值
cout << *age_ptr << endl;
cout << *(age_ptr + 1) << endl;
cout << *(age_ptr + 2) << endl;
cout << *(age_ptr + 3) << endl;
cout << *(age_ptr + 4) << endl;
cout << *(age_ptr + 5) << endl;
return 0 ;
}
4.2 指针递增递减
#include <iostream>
using namespace std;
/*
* 指针递增递减
*/
int main() {
int age []{18,9,20,33,55,66};
int *age_ptr{age};//声明指针,存放的是数组第一个元素的地址
//指针递增
for(int i = 0; i<sizeof(age)/sizeof (int); i++){
cout << age_ptr << endl;
cout << *age_ptr << endl;
age_ptr++; // 指针移动指向下一个元素
}
cout << "------------" << endl;
//指针递减
int age2[] = {18,9,20,33,55,66};
int lenghts = sizeof (age2) / sizeof (int);
int *age_ptr2 = &age2[lenghts-1];//声明指针,存放的是数组最后一个元素的地址
for(int i = lenghts-1; i>=0; --i){
cout << age_ptr2 << endl;
cout << *age_ptr2 << endl;
age_ptr2--; // 指针移动指向下一个元素
}
return 0 ;
}
4.3 指针的等价判断
#include <iostream>
using namespace std;
/*
* 等价判断
*/
int main() {
string s1{"张三"};
string s2{"张三"};
string *p1 {&s1};
string *p2 {&s2};
string *p3 {&s1};
cout << (p1 == p2) <<endl;
cout << (p1 == p3) <<endl;
cout << (*p1 == *p2) <<endl;
cout << (*p1 == *p3) <<endl;
return 0 ;
}
5.指针与常量
5.1 指针常量
#include <iostream>
using namespace std;
/*
* 指针常量
*/
int main() {
//定义高分和低分的变量
int high = 20;
int low = 2;
//使用指针常量指向高。这里的const修饰的是指向的数据
const int *p = &high;
//不允许修改值,因为此时编译器会认为high是一个常量
// *p = 86 ; // 错误
//可以指向其他位置。
p = &low ; // 正确
//当然也可以通过变量方式来修改值。
high = 88;
return 0 ;
}
5.2 常量指针
#include <iostream>
using namespace std;
/*
* 常量指针
*/
int main() {
//定义高分和低分的变量
int high = 20;
int low = 2;
//表示这个指针是一个常量,这里的const修饰的是指针
int *const p = &high;
//允许修改值,但是不能做其他指向
*p = 86;
// p = &low ; // 错误
return 0 ;
}
5.3 常量指针指向常量
#include <iostream>
using namespace std;
/*
* 常量指针指向常量
*/
int main() {
//定义高分和低分的变量
int high = 20;
int low = 2;
//第一个const是修饰指向的数据,第二个const是修饰指针。
//表示不管是指针还是指向的数据,都是常量
const int *const p = &high;
//既不允许修改指向,也不允许修改指向的值
*p = 86 ; // 错误
p = &low ; // 错误
return 0 ;
}
6.函数参数传递指针
函数的参数,除了可以传递普通的变量和引用之外,还可以把指针当成参数来传递。
#include <iostream>
#include <string>
using namespace std;
/*
* 函数传递指针
*/
void changedata(int *p){
*p = 10;
}
void sway(int *m,int *n){
int temp = *m;
*m = *n;
*n = temp;
}
int main() {
int data = 2;
cout<<"修改前data的值:"<<data<<endl;
changedata(&data);
cout<<"修改后data的值:"<<data<<endl;
//实现两个值的交换
int x = 100,y = 200;
cout<<"X的值:"<<x<<" Y的值:"<<y<<endl;
sway(&x,&y);
cout<<"修改后X的值:"<<x<<" 修改后Y的值:"<<y<<endl;
return 0;
}
7.函数返回指针
#include <iostream>
#include <string>
using namespace std;
/*
* 函数返回指针
*/
int *get_larger(int *m, int *n){
//利用解引用获得数据进行比较
if(*m > *n){
return m;
} else{
return n;
}
}
int main() {
int i = 100;
int j = 200;
int *larger = get_larger(&i,&j);
cout<<*larger<<endl;
return 0;
}
- 注意:不要返回一个函数内部的一个局部变量指针 , 因为本地变量的声明周期应该只位于函数内部。一旦函数执行完毕则被释放。
#include <iostream>
#include <string>
using namespace std;
/**
* 返回一个函数内部的局部变量指针
*/
int *fun(){
int number = 99;
return &number;
}
int main() {
//会报错
int *result = fun();
cout<<*result<<endl;
return 0;
}
8.二级指针
8.1 二级指针的定义使用
#include <iostream>
#include <string>
using namespace std;
/**
* 二级指针的使用
*/
int main() {
int age = 10;
int *p1 = &age;
int **p2 = &p1;
cout<<"age:"<<age<<endl;
cout<<"&age:"<<&age<<endl;
cout<<"p1:"<<p1<<endl;
cout<<"*p1:"<<*p1<<endl;
cout<<"&p1:"<<&p1<<endl;
cout<<"p2:"<<p2<<endl;
cout<<"*p2:"<<*p2<<endl;
cout<<"**p2:"<<**p2<<endl;
return 0;
}
8.2 二级指针的应用
- 通常出现的地方是作为函数参数传递。如果在函数的内部想要修改外部一级指针指向的数据值,那么则需要二级指针了。
- 如下所示,如果传递的是一个一级指针,那么在外部的p依然没有被分配空间,传递进去的依然是一份值的拷贝而已 。
#include <iostream>
#include <string>
using namespace std;
/**
* 二级指针的应用
*/
void createP(int **p){
*p = new int();//在堆内存中开辟一块内存。
cout<<"函数中*p:"<<*p<<endl;
}
int main() {
//声明一个空指针
int *p = nullptr;
createP(&p);//使用二级指针在函数中完成在堆内存中申请内存
*p = 100;
cout<<"p:"<<p<<endl;
cout<<"*p:"<<*p<<endl;
//释放指针
delete p;
return 0;
}
9.函数指针
9.1 函数指针的基本使用
#include <iostream>
#include <string>
using namespace std;
/**
* 使用函数指针方式调用函数
*/
int add(int m,int n){
return m+n;
}
int main() {
//使用函数指针调用函数 前半段表示声明一个函数指针add_ptr 该函数指针指向的函数返回值是int,并且有两个int类型的参数。
int (*add_ptr)(int ,int) = add;
int result = add_ptr(10,20);
cout<<"使用函数指针调用的函数:"<<result<<endl;
//使用普通方法
int result2 = add(10,20);
cout<<"使用普通方法调用函数:"<<result2<<endl;
return 0;
}
9.2 函数指针作为参数的使用
函数虽然不能直接作为参数来进行传递,但是函数指针可以。
#include <iostream>
#include <string>
using namespace std;
int add(int m, int n);
int calc(int a,int b, int(*fun)(int ,int));//函数指针作为参数
int main() {
int result = calc(10,20,add);
cout<<"result:"<<result<<endl;
return 0;
}
int add(int m,int n){
return m+n;
}
int calc(int a,int b,int(*fun)(int,int)){
return fun(a,b);
}
9.3 函数指针的作用
如果一个通用的函数,需要使用到另一个函数,但是这个函数并没有确定名称,是由其他组织或者个人开发的,那么这时候可以预留一个位置,做成函数指针虚位以待。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
/**
*函数指针的作用
*/
//函数原型
void listPrint(vector<int> scores, void(*ps)(vector<int>));
void print1(vector<int> s);
void print2(vector<int> s);
void print3(vector<int> s);//未知的函数实现
int main() {
vector<int> s = {10,20,30,50,66,99};
listPrint(s,print1);//调用的时候只需要输入数据和使用的函数名字就行
listPrint(s,print2);
return 0;
}
/**
* 接收函数指针的函数
* @param scores 数据
* @param ps 函数的指针
*/
void listPrint(vector<int> scores, void(*ps)(vector<int>)){
ps(scores);
}
/**
* 打印函数
* @param s
*/
void print1(vector<int> s){
cout << "使用for遍历" << endl;
for(int i : s){
cout << i << endl;
}
}
/**
* 打印函数
* @param s
*/
void print2(vector<int> s){
cout <<"使用fori的方式"<< endl;
for (int i = 0; i < s.size(); ++i) {
cout <<s[i] << endl;
}
}
void print3(vector<int> s){
//未知的函数实现
}
10.引用
10.1 引用的简单使用
#include <iostream>
#include <string>
#include <vector>
using namespace std;
/**
*引用
*/
int main() {
int a = 30;
int &b = a;
b = 33;//同时也改变了a的值
cout<<"a:"<<a<<endl;
cout<<"b:"<<b<<endl;
vector<string> names = {"zhangsan","lisi"};
for(auto n:names){
n = "aa";//这里不会更改vector的值
}
for(auto n:names){
cout<<"n:"<<n<<endl;
}
for(auto &n:names){
n = "bb";
}
for(auto n:names){
cout<<"n:"<<n<<endl;
}
return 0;
}
10.2 左值引用
平常所说的引用,实际上指的就是左值引用 lvalue reference , 常用单个 & 来表示。 左值引用只能接收左值,不能接收右值。const 关键字会让左值引用变得不同,它可以接收右值
#include <iostream>
#include <string>
#include <vector>
using namespace std;
/**
*左值引用
*/
//只能把左值赋值给引用,不能把右值赋给引用
int square(int &n){
return n * n ;
}
int main(){
int x = 1000; // x是一个左值, 而 1000 是一个右值。
x = 1000 + 20 ; //x 是左值, 1000 + 20 是右值,
int b = 10; // b 是一个左值, 10 是一个右值
b = x ; //b是一个左值, 而 x依然是一个左值。
int num = 10 ;
square(num) ; //正确
square(5); //错误。因为5是右值 ,不能赋值引用。
return 0;
}
10.3 右值引用
右值引用一般用于绑定到一个即将销毁的对象,所以右值引用又通常出现在移动构造函数中。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
/**
*右值引用
*/
int main() {
int i = 66;
int &r = i ; //r 是一个左引用,绑定左值 i
int &&rr = i ; //rr是一个右引用,绑定到左值i , 错误!
int &r2 = i*42 ; // r2 是一个左引用, 而i*42是一个表达式,计算出来的结果是一个右值。 错误!
const int &r3 = i*42; // 可以将const的引用,绑定到右值 正确
int &&rr2 = i*42 ; // 右引用,绑定右值 正确
return 0 ;
}
10.4 左右值引用总结
左值具有持久的状态,有独立的内存空间,右值要么是字面常量,要么就是表达式求值过程中创建的临时对象。
结束