• C++语法基础(8)——指针与引用


    指针和引用

    指针基本运算

    指针简介

    指针的概念
    指针保存了一个内存地址,对指针的操作就是对地址的操作。
    可以将内存理解为一个“大数组”,指针相当于存储了一个数组下标,它指向下标对应位置的变量。
    在这里插入图片描述
    指针的声明
    &运算符被称为取地址运算符,它返回变量在内存中的地址。void指针可以指向任意类型的值。

    int a;
    int *ptrToA = &a;
    
    double b;
    double *ptrToB = &b;
    
    void *voidPtrToA = &a;
    void *voidPtrToB = &b;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    取地址运算符不能作用于常量或表达式,如int *ptr = &2; 或者int *ptr = &(a + b); 。因为他们在内存中并没有固定的地址。
    空指针
    空指针的值可以用NULL(C和C++98的风格)或者nullptr(C++11新标准)表示。他们的值都是0x0,表示指针不指向任何对象。

    //cite from <stddef.h>
    #ifndef __cplusplus
    #define NULL ((void *)0) // C语言
    #else
    #define NULL 0 // C++
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以发现,C语言的NULL保证为void*类型,但C++的NULL仅为常量0。在C++中使用NULL宏可能会导致函数重载错误。相比之下, C++11引入的nullptr始终保证其为指针类型。
    在C++代码中,建议使用nullptr

    指针的基本运算

    指针赋值

    int *intp = &x; //让指针指向某一变量
    
    int *intp1 = &x, *intp2;
    intp2 = intp1; // 同类型的指针之间可以赋值,赋值相当于改变了指针指向的对象。
    
    • 1
    • 2
    • 3
    • 4

    间接访问
    为了访问指针指向的值,我们使用*符号,这被称为解引用运算符:

    • *ptr的值为指针指向的变量的值;
    • *ptr的修改会作用到原对象上。

    注意声明int *ptr = &a;中的*并不是解引用运算符,它是类型声明的一部分。
    void类型

    • void*指针可以指向任何类型的值。
    • void*类型不可以解引用。

    事实上,使用void*代表着你放弃了所有类型检查和类型安全性。因此,除非必要,不建议在C++代码中使用void*类型。

    int a = 233;
    int *ptrToA = &a;
    void *voidPtrToA = &a;
    
    cout << *ptrToA << endl; // 233
    cout << ptrToA << endl; //一个十六进制数,表示内存中的位置。例如0x7ffd99314e64
    
    *ptrToA = 466;
    cout << a << endl; // 466
    
    *voidPtrToA = 699; // Compile Error: ‘void*’ is not a pointer-to-object type
    
    int c = 1, *ptrToC = &c;
    ptrToA = ptrToC; // 现在ptrToA指向了变量c
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    reinterpret_cast
    对于一个指向类型A的指针,我们可以将其转换成一个指向类型B的指针。此时,指针指向的位置没有变,只是对于内存中数据的解释方式变了。

    转换的方式便是B *ptr2 = reinterpret_cast<B *> ptr1;

    如以下代码,表示用一个float指针解释一个内存中的int变量:

    int x = 1;
    float *fp = reinterpret_cast<float *> &x; 
    
    • 1
    • 2

    要注意的是转换后的类型,它的有效长度不能比原来的类型更长。比如说int类型为 4 byte,double类型为 8 byte。将一个指向int类型的指针转换成指向double类型的指针,这在语法上没有问题,但是如果解引用得到的指针,double多出的 4 byte 的数据是无意义的。

    指针与数组

    数组名其实就是指向数组第0个元素的指针。但是,不能修改“数组名”这个指针的值,即它是常量指针

    int a[] = {1, 2, 3};
    *a; // 等同于a[0]
    *(a + n); // 等同于a[n]
    
    int *p = a + 1; 
    *p; // 2
    *(p + 1); // 3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    指针运算

    • 当指针指向数组元素时,加减法才有意义。
    • 可以对指针加上或者减去一个整数。这表示:将指针在数组中向前或向后移动若干位置。
    • 当两个指针指向同一个数组时,可以对两个指针做减法。这表示两个指针所指向元素在数组中的距离。
    p = p + 1; // *p == 3
    p = p - 1; // *p == 2
    cout << p - a << endl; // 1
    
    • 1
    • 2
    • 3
    • 指针在偏移后不能超过数组的范围。当对超过数组范围的指针解引用时,行为未定义(可能出现运行时错误)。
    • 同样,也不要对不在同一个数组内的两个指针执行减法。

    动态内存

    动态内存分配

    // 定义一个指针,申请一块内存,地址存入指针,通过指针间接访问动态申请的内存
    int *scores;
    scores = 内存的起始地址;
    
    • 1
    • 2
    • 3

    申请动态变量

    p = new type; // 申请动态变量
    p = new type[size]; // 申请动态数组
    p = new type(初值); // 申请动态变量并初始化
    p = new int[5]{1,2,3,4,5}; //申请动态数组并初始化
    
    • 1
    • 2
    • 3
    • 4

    释放动态变量的空间

    // 动态变量的空间必须由程序释放
    delete p; // 释放动态变量
    delete [] p; // 释放动态数组
    // 字符数组可以不加方括号
    
    • 1
    • 2
    • 3
    • 4

    实例

    int main()
    {
    	int *p;
    
    	p = new int;
    	if (!p) {
    		cout << "allocation failure\n";
    		return 1;
    	}
    	*p = 20;
    	cout << *p;
    	delete p;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • new Type(程序的静态存储区)上新建一个对象,返回指向这个对象的指针。
    • new Type[cnt]上新建一个长为cnt,类型为Type的数组,返回这个数组的首指针。

    new运算符的初始化:

    int *p1 = new int; // *p1为不确定的任意值
    int *p2 = new int(3); // *p1 == 3
    int *p3 = new int(); // *p1 被初始化为0
    
    int *p4 = new int[4]; // p4数组的所有元素为不确定值
    int *p5 = new int[4] {1, 2, 3, 4}; // 用花括号列表初始化
    int *p6 = new int[4] (); // p6数组的所有元素被初始化为0
    int *p7 = new int[4] {1}; // 注意:p7数组仅有第0个元素为1;其余元素都被初始化为0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    动态变量实例

    在这里插入图片描述

    指针与字符串

    • 字符串的常用表示是指向字符的指针
    char *String; // 严格说是错误的(编译器会提示warning,但可以运行),此时若修改字符串的任一位,行为未定义(undefined)。应为const char *String; 
    String = "abcde"; // 字符串常量的每一个字符不可以修改
    
    char *String, ss[] = "abcdef";
    String = ss;
    
    char *String;
    String = new char[10];
    strcpy(String, "abc");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    用指针处理字符串——编写一个统计字符串中单词的个数的函数

    #include <ctype>
    using namespace std;
    int word_cnt(const char *s)
    {
    	int cnt = 0;
    
    	while (*s != '\0') {
    		while (isspace(*s)) ++s; //跳过空白字符
    		if (*s != '\0') {
    			++cnt; //找到一个单词
    			while (!isspace(*s) && *s != '\0') ++s; //跳过单词
    		}
    	}
    	return cnt;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    指针与函数

    编一函数,交换二个参数值

    1.并没有交换成功:

    void swap(int a, int b)
    {
    	int c;
    	c=a; a=b; b=c;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.用指针传递

    void swap(int *a, int *b)
    {
    	int c;
    	c=*a; *a=*b; *b=c;
    }
    
    int main() 
    {
        int a = 1, b = 2;
        swap(&a, &b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    求解一元二次方程的函数

    如何让此函数返回二个跟——指针传递

    • 由调用程序准备好存放两个跟的变量
    • 将变量地址传给函数
    • 函数将两个根的值分别放入这两个地址

    函数原型

    void SolveQuadratic(double a, double b, double c, double *px1, double *px2)
    
    • 1

    函数调用

    SolveQuadratic(1.3,4.5,2.1,&x1,&x2)
    SolveQuadratic(a,b,c,&x1,&x2)
    
    • 1
    • 2

    两类函数参数

    输入参数:用值传递
    输出参数:用指针传递
    在参数表中,输入参数放在前面,输出参数放在后面
    
    • 1
    • 2
    • 3

    如何获知方程有根、没根
    让函数返回一个整型数。该整型数表示解的情况:

    • 0:两个解
    • 1:一个解
    • 2:无解
    • 3:不是一元二次方程

    完整的函数

    int SolveQuadratic(double a, double b, double c, double *px1, double *px2)
    {
    	double disc,sqrtDisc;
    
    	if(a==0) return 3; //不是一元二次方程
    	disc = b * b - 4 * a * c;
    	if(disc < 0) return 2; //无根
    
    	if (disc == 0) {*px1 = -b/(2*a);return 1;} //等跟
    	//两个不等根
    	sqrtDisc = sqrt(disc);
    	*px1 = (-b + sqrtDisc) / (2 * a);
    	*px2 = (-b - sqrtDisc) / (2 * a);
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    函数的使用

    int main()
    {
    	double a,b,c,x1,x2;
    	int result;
    
    	cout << "请输入a,b,c:";
    	cin >> a >> b >> c;
    
    	result = SolveQuadratic(a,b,c,&x1,&x2);
    	switch (result) {
    		case 0:cout<<"方程有两个不同的根:x1="<<x1<<"x2 = " << x2; break;
    		case 1:cout<<"方程有两个等根:"<<x1;break;
    		case 2:cout<<"方程无根"break;
    		case 3:cout<<"不是一元二次方程";
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    返回指针的函数

    • 类型 *函数名(形式参数表);
    • 返回值是某个变量的地址

    实例

    • 设计一个函数从一个字符串中取出一个子串

    原型设计

    参数:从哪一个字符串中取子串、起点和终点
    返回值:取出的子串
    字符串可以用一个指向字符的指针表示,所以函数的返回值是一个指向字符的指针
    
    • 1
    • 2
    • 3
    char *subString(const char *s, int start, int end)
    {
    	int len = strlen(s);
    	if (start<0||start>=len||end<0||end>=len||start>end) {
    		cout<<"起始或终止位置错"<<endl;
    		return NULL;
    	}
    	char *sub = new char[end - start + 2];
    	strncpy(sub, s +start, end - start + 1);
    
    	return sub;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    调用subString的函数必须释放空间!!!

    char *pc = subString(str, 3, 10);
    out << pc << endl;
    delete pc;
    
    • 1
    • 2
    • 3

    引用

    引用概念

    引用——给某个变量取一个别名,使一个内存单元可以通过不同的变量名来访问
    定义格式

    类型 &变量名 = 变量名;
    
    int i;
    int &j = i;
    j是i的别名,i与j是同一个内存单元。这个绑定关系在j的生命周期中不能改变
    
    • 1
    • 2
    • 3
    • 4
    • 5

    常量引用

    const 类型 &变量名 = 变量名;
    
    int i;
    const int &j = i;
    
    • 1
    • 2
    • 3
    • 4
    • 控制变量的修改,不管被引用对象是常量还是变量,用此名字是不能赋值

    • 如i=5是合法的,j=5是非法的

    • const引用的初值可以是常量或表达式。如:

    • const int &y=2+5;√ int &y=2+5; ×

    • 引入引用的主要目的是将引用作为函数的参数

    • 引用可以理解为变量的“别名”。同时,也可以理解为一个type *const指针,即指针指向的对象的值可变,但指针本身的地址不可变。在指针的基础上,引用省略了取地址和解引用。

    • 对引用的操作(求值,修改等)始终绑定在原对象上。

    int a = 1;
    int &b = a; // a == 1; b == 1
    a = 2; // a == 2; b == 2
    b = 3; // a == 3; b == 3
    
    cout << sizeof(b) << endl; // 4; 和sizeof(a)相同
    cout << &b << ' ' << &a << ' ' << (&a == &b) << endl; // true; a和b的地址是一样的
    
    int *c = &b; // 此时c指向a
    cout << sizeof(c) << endl; // 64位系统上为8; 和上面sizeof(b)做对比
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    引用传递

    // 指针参数
    void swap(int *m, int *n)
    {
    	int temp;
    	temp=*m;
    	*m=*n;
    	*n=temp;
    }
    
    swap(&x,&y);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    // 引用参数
    void swap(int &m,int &n)
    {
    	int temp;
    	temp=m;
    	m=n;
    	n=temp;
    }
    
    swap(x,y);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    以上引用传递相当于发生了如下变量定义:

    int &m = x;
    int &n = y;
    
    • 1
    • 2
    • 引用参数传递时实参必须是变量,而不能是一个表达式或常量!!!
    • 引用传递减少函数调用时的开销作为输出参数
    • 在C++中,函数参数一般都采用引用传递

    按值传递大对象时,会导致对整个对象的拷贝,可能很慢;而按引用传递时,开销仅仅为传一个指针(64 位计算机中为 8 bytes)。

    struct BigType 
    {
        int val[1000];
    };
    void func1(BigType a) // copies 4000 bytes
    { 
        // ...
    }
    void func2(const BigType &a) // copies 8 bytes
    { 
        // ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    引用返回

    • 格式:类型名 &函数名(形式参数表)
    • 函数调用的是return后面的变量的别名
    • 减少函数返回时的开销
    • 讲函数用于赋值运算符的左边,即作为左值
    int a[] = {1,3,5,7,9};
    int &index(int); //声明返回引用的函数
    void main()
    {
    	index(2) = 25; //将a[2]重新赋值为25
    	cout << index(2);
    }
    int &index(int j)
    { return a[j]; } //函数是a[j]的一个引用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    返回值必须是一个离开函数后依然存在的变量!!!

    函数也可以返回变量的引用,此时和指针类似。可以返回的引用包括:

    • 对全局变量的引用
    • 返回函数的引用类型参数
    • 返回函数指针类型参数解引用之后的结果

    同上,不能返回局部变量的引用。

    int glob;
    int& func(int a, int &b, int *c) 
    {
        int tmp;
        return a; // err
        return tmp; // err
        return b; // ok
        return *c; // ok: *c为引用类型
        return glob; // ok
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    Flink SQL --- 窗口聚合
    Java使用DOM简单解析XML文件
    Word控件Spire.Doc 【文本】教程(21) ;如何在 C# 中用 Word 文档替换文本
    [附源码]Python计算机毕业设计Django高校体育场馆管理系统
    设计模式-行为性模式:策略模式
    Windows无法访问指定设备、路径或文件怎么办?
    【Linux operation 38】解决Linux 端口被占用
    从字节码层面解析Java语言--i与i--的区别
    学习Java的高级特性
    Shell中的$@和$*的区别
  • 原文地址:https://blog.csdn.net/qq_44941689/article/details/125588949