引用、动态内存分配、函数、结构体

引用

定义和初始化

**数据类型 &引用名 = 目标名;**

引用和目标共用同一片空间(相当于对一片空间取别名)。
引用的底层实现:数据类型 * const p; ------> 常指针

int const *p; -----> 修饰 *p
const int *p; -----> 修饰 *p
int *const p; -----> 修饰 p
const int *const p; -----> 修饰 *p 和 p

& 的使用

1、取变量的地址
2、位与运算符
3、&& 逻辑与
4、定义引用(如果 & 前面有数据类型,就说明是在定义引用)

常引用

const <数据类型> &<变量名> = 常数;
<数据类型> const &<变量名> = 常数;

e.g. const int &num = 15;

作用:保护目标不能通过引用修改。

#include <iostream>
using namespace std;

int main()
{
    int a = 90;
    const int &r1 = a;  	// 常引用 r1 引用了 a
    a = 100;    			// 可以通过变量本身修改自身
    // r1 = 20;  			// 报错,不能通过常引用修改目标
    cout << "a = " << a << endl;    
    return 0;
}

在这里插入图片描述
在这里插入图片描述

引用的性质

1、定义引用时必须初始化(不能初始化为 NULL);
2、访问引用相当于访问目标;
3、引用的目标一旦指定,不能修改;
4、引用和目标占用同一片空间(引用不会额外开辟空间);

#include <iostream>
using namespace std;

int main()
{
    int num1 = 30, num2 = 90;
    // int &ref2;   		// 报错,定义引用的同时必须初始化
    int &ref1 = num1;   	// 定义了一个引用 ref1,目标是 num1
    						// 此后,访问 ref1 和访问 num1 是同一个效果
    
    // 尝试修改 ref1 的目标
    ref1 = num2;   			// <===> num1 = num2; 
    cout << ref1 << endl;	// 结果为 90,此时 num1 == 90
    cout << &num1 << endl;
    cout << &ref1 << endl;	// 引用和目标占用同一片空间
}

在这里插入图片描述

5、将变量的引用的地址赋值给一个指针,此时指针指向的还是原来的变量;

#include <iostream>
using namespace std;

int main()
{
    int a = 1;
    int &b = a;
    int *c = &b;

    cout << a << " " << &a << endl; 	// 1 0x61fe84
    cout << b << " " << &b << endl; 	// 1 0x61fe84
    cout << *c << " " << c << endl; 	// 1 0x61fe84
    return 0;
}

6、可以对指针建立引用。

#include <iostream>
using namespace std;

int main()
{
    int a = 1;
    int &b = a;
    int *c = &b;
    int *&d = c; 						// d是对c的引用

    cout << a << " " << &a << endl; 	// 1 0x61fe84
    cout << b << " " << &b << endl; 	// 1 0x61fe84
    cout << *c << " " << c << endl; 	// 1 0x61fe84
    cout << *d << " " << d << endl; 	// 1 0x61fe84
    return 0;
}

引用和指针的区别

1、引用定义必须初始化,指针定义可以不初始化(野指针);
2、指针可以指向 NULL,引用的目标不可以为空;
3、可以改变指针的指向,不能改变引用的目标;
4、存在指针数组,但是不存在引用数组;
5、指针会额外开辟空间,引用不会额外开辟空间;
6、有多级指针,没有多级引用;

指针、数组的引用

指针的引用

数据类型 *(&引用名) = 指针名;

数组的引用

数据类型 (&引用名)[数组长度] = 数组名;

#include <iostream>
using namespace std;

int main()
{
    int arr[5] = {89, 12, 34, 0, 5};
    int *p = arr;
    int *(&r3) = p;   			// 定义了一个指针 p 的引用 r3
    cout << *r3 << endl;
    cout << *(r3+1) << endl;
    
    // 定义一个指向整个一维数组的指针
    // int (*p)[5];

    // 定义了一个 arr 数组的引用 r2
    int (&r2)[5] = arr;    		// arr 的数据类型 int [5]
    cout << *r2 << endl;		// r2[0]
    cout << r2[1] << endl;
    return 0;
}

在这里插入图片描述

引用实现数组传参

利用引用实现数组的传参,并冒泡排序。

#include <iostream>
using namespace std;

void mysort(int (&ref)[8])
{
    int temp;
    for (int i = 0; i < 8-1; i++)
        for (int j = 0; j < 8-1-i; j++)
        {
            if (ref[j] > ref[j+1])
            {
                temp = ref[j];
                ref[j] = ref[j+1];
                ref[j+1] = temp;
            }
        }
}

int main()
{
    int arr[8] = {8, 6, 2, 5, 7, 3, 1, 4};
    mysort(arr);

    for (int i = 0; i < 8; i++)
        cout << arr[i] << ' ';
    cout << endl;

    return 0;
}

引用作为函数的形参

优点:
1、不需要额外开辟空间;
2、不涉及到值传递和地址传递的问题,传到函数内部的就是实参本身。

#include <iostream>
using namespace std;

void my_swap(int &x, int &y)
{
    // 由于形参是引用变量,所以该函数内对形参的修改也是对主函数实参的修改
      int temp;
      temp = x;
      x = y;
      y = temp;
}
int main()
{
    int num1 = 30, num2 = 90;

    my_swap(num1, num2);
    cout << "调用函数后:" << endl;
    cout << "num1 = " << num1 << endl;
    cout << "num2 = " << num2 << endl;

    return 0;
}

在这里插入图片描述

引用作为参数进行定义的时候,是不会产生副本的,这样会提高代码的运行效率,因此在正常编程中,建议使用引用进行传递参数。
在 引用形参 不参与计算的情况下,建议使用 const 修饰引用形参,以达到引用的安全性。
在这里插入图片描述

引用作为函数的返回值

类比:指针函数

以指针作为函数的返回值,返回长生命周期的变量的地址。
1、全局变量(静态区)的地址;
2、static 修饰的局部变量(静态区)的地址;
3、申请的堆区空间的地址;
4、常量区空间的地址;
5、实参传递过去的地址。

推理:引用作为函数的返回值

和指针函数一样,将引用作为函数的返回值,需要返回长生命周期的变量的引用。
引用作为函数的返回值,是一个左值(因为返回引用就是返回变量本身),可被赋值和自增自减运算。

#include <iostream>
#include <cstdlib>
using namespace std;

int num1 = 12;
int &fun(int *p1)   	// fun(&a);  <==> int *p = &a; p 指向 a
{ 	
    cout << p1 << endl;
    return ++(*p1);   		// p1 是一个 4 Bytes 的指针,需要返回 4 Bytes 的整型
}

int &fun1()
{
    return num1;		// <==> 返回 num1 的引用
}

int main()
{
    int a = 90;
    int *p1 = (int *)malloc(4);
	cout << p1 << endl;
    
    cout << num1 << endl;   		// 12
    fun1()++ ;  					// <==> num1++;

    cout << num1 << endl;			// 13
    *p1 = 90;
    fun(p1);
    cout << *p1 << endl;

    return 0;
}

在这里插入图片描述

动态内存分配

C 中的动态内存分配:malloc 和 free 在 C++ 中可以继续使用。

new

单个内存空间的申请

申请

数据类型 *指针名 = new 数据类型;
// new 会按照数据类型申请空间

申请并初始化

数据类型 *指针名 = new 数据类型(初始数据);
// 申请到空间中的初始值就是 () 内的数据

多个内存空间的申请

数据类型 *指针名 = new 数据类型[size]{若干初始数据};
// 使用 new 申请多个空间,仍然可以使用不完全初始化的方式 // ,未初始化的部分默认为 0

delete

单个内存空间的释放

delete 指针名;

多个内存空间的释放

delete [] 指针名;

#include <iostream>
using namespace std;

int main()
{
    int *p = new int(3);    		// 指针 p 指向堆区申请的一个 int 的空间
    cout << *p << endl;
    string *ps = new string("Hola~");
    cout << *ps << endl;

    float *ptr = new float[5]{2.1, 3, 5, 6.2};   // 申请了 5 个 float 的空间
    int i;
    for (i = 0; i < 5; i++)
        cout << *(ptr+i) << endl;

    delete p;     				// 释放单个的堆空间
    p = NULL;  					// NULL 的 ASCII 是 0,可能会满足整型 0 的情况,最好不使用

    delete [] ptr;   	// 释放多个堆空间,需要使用[],[]内不需要写任何内容
    ptr = nullptr;		// C++ 中提供的 nullptr,只表示指针为空的情况

    delete ps;
    ps = nullptr;

    return 0;
}

在这里插入图片描述

C++ 中不推荐使用 malloc 和 free:

malloc / free 不会自动调用 构造函数 / 析构函数;
new / delete 会自动调用 构造函数 / 析构函数。

new/delete 和 malloc/free 的区别

1、malloc 和 free 是库函数,new 和 delete 是关键字;
2、 使用 malloc 申请空间时,需要强转,还需要计算申请空间的大小;
使用 new 不需要强转,也不需要求大小;
3、malloc 按字节申请空间,new 按数据类型申请空间;
4、malloc 申请时不能执行初始化操作,new 可以申请的同时初始化;
5、delete 在释放空间时,需要考虑是单个空间还是多个空间,free 不需要考虑空间问题;
6、malloc 不会调用构造函数,new 会调用构造函数;
7、free 不会调用析构函数,delete 会调用析构函数。

函数

函数重载

概念

实现一名多用,解决同一功能的函数因为参数类型或个数不同,需要多次定义不同名函数的问题。
函数重载属于静态多态的一种。

定义要求

1、函数名相同;
2、形参不同(可以是个数不同,也可以是类型不同,还可以是顺序不同);
3、作用域相同;
4、若函数仅有返回类型不同,然而不满足前三条,则不构成重载;
5、若函数的参数列表中有参数被 const 修饰,然而不满足前三条,则不构成重载;

#include <iostream>
using namespace std;

int add(int x, int y)
{
    return x + y;
}

float add(float x, float y)
{
    return x + y;
}

string add(string x, string y)
{
    return x + y;
}

int main()
{
    int a = 3, b = 7;
    float c = 2, d = 1.2;
    string str1 = "Hello ", str2 = "world!";

    cout << add(a, b) << endl;
    cout << add(c, d) << endl;
    cout << add(str1, str2) << endl;

    return 0;
}

运行结果如下:
在这里插入图片描述

可以通过 g++ -S xxx.cpp -o xxx.s ----->查看 xxx.s 可以看到
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

函数的默认参数

C 函数参数的获取方式:在函数被调用时,获取传递过来的实参(都是从主调函数处获取的)。

C++中支持函数的默认参数:

在定义函数时,可以给某一些形参添加默认值,
调用函数时,如果默认参数处有实参传过来,则使用实参的值;
调用函数时,如果没有给默认参数传参,则该参数使用默认值。

#include <iostream>
using namespace std;

float add(float x, float y, float z = 1.8)
{
    return x + y + z;
}

int main()
{
    float c = 2, d = 1.2;

    cout << add(c, d) << endl;				// 2 + 1.2 + 1.8 = 5
    cout << add(c, d, 14) << endl;			// 2 + 1.2 + 14 = 17.2

    return 0;
}

默认参数必须遵循靠右原则:

若某一个参数有默认参数,则该参数右侧的所有参数一定有默认参数。因为函数传参时遵循靠左原则。

// 错误示范

float add(float x, float y = 1.8, float z)			// 报错
{
    return x + y + z;
}

在这里插入图片描述

避免函数重载和默认参数同时出现:

#include <iostream>
using namespace std;

float add(float x, float y)
{
    return x + y;
}

float add(float x, float y, float z = 1.8)
{
    return x + y + z;
}

int main()
{
    float c = 2, d = 1.2;

    cout << add(c, d) << endl;			// 报错
    cout << add(c, d, 14) << endl;

    return 0;
}

在这里插入图片描述

默认参数尽量写在函数声明处

如果函数的定义和声明是分开的,默认参数只能出现在一个位置。

#include <iostream>
using namespace std;

namespace A {
    void fun(string str = "hello");		// ①
}

void A::fun(string str)					// ②
{                                    	// 默认参数只能设置在 ① 或 ② 处,两处都写则报错
    cout << str << endl;
}

int main()
{
    A::fun();
    A::fun("hey");

    return 0;
}

在这里插入图片描述

哑元

在函数的参数列表中,某个参数只有数据类型而没有变量名,则称此参数为“哑元”。
虽然在函数内获取不到哑元参数,但在调用函数时,仍要对其传参

#include <iostream>
using namespace std;

float add(float x, float, float z = 1.8)		// 第二个参数是哑元
{
    return x + z;
}

int main()
{
    float c = 2, d = 1.2;

    cout << add(c, d) << endl;					// 在 add函数 内接收不到 d 的值	
    cout << add(c, d, 14) << endl;				// 在 add函数 内接收不到 d 的值	

    return 0;
}

在这里插入图片描述

哑元的使用场合:

1、保证代码的向下兼容

对大型的程序的某个功能进行更新,且需要保证代码的向下兼容(前面版本的代码仍然可以使用)时,就可以将某些参数设定为哑元。

2、运算符重载时,自增自减运算符的重载。
3、用来区分函数重载
#include <iostream>
using namespace std;

// 哑元函数
void print_show(int)
{
    cout << "(int)" << endl;
}

void print_show(int i, int)
{
    cout << "(int i, int)" << endl;
}

int main()
{
    print_show(5);			// 调用第一个函数
    print_show(5, 1);		// 调用第二个函数
    return 0;
}

在这里插入图片描述

内联函数(inline)

内联函数用于取代 C 语言中宏定义的函数,正确使用内联函数可以提升程序的执行效率。内联函数在编译时候,直接把函数体展开到主函数中编译,在运行期间可以减少调用的开销。(以空间换时间)
通常具有以下性质的函数可以写为内联函数:
● 代码长度5行以内
● 不包含复杂的控制语句
● 频繁被调用

inline 函数类型 函数名(参数列表);

把函数体在调用处展开。(函数调用 ——> 顺序执行)

优缺点:

可以提高运行效率(函数调用时没有压栈和出栈的过程),但可能会造成代码膨胀。

适用条件:

函数被频繁调用,函数体尽量小。

是否展开成内联函数?

手动添加的 inline 关键字只是给编译器的一个建议
如果加了 inline 关键字,但是编译器认为效率不会提高,函数就不会被展开成内联函数;
如果编译器认为展开为内联函数会提高效率,则无论是否加 inline 都会将函数展开为内联函数。

结构体

C++ 中的结构体 和 C 中结构体 的区别

1、C++ 中定义结构体变量,不需要加 struct;
2、C++ 中结构体内,可以定义函数;
3、C++ 中结构体内,可以给结构体成员初始化;
4、C++ 中结构体内,可以定义另一个结构体类型;
5、C++ 中结构体成员,可以设置访问权限(默认为 public);
6、C++ 中的结构体可以继承;
7、当 C++ 结构体中有引用成员时,有两种初始化方法:
① 给结构体中的引用成员一个初始值;
② 定义结构体变量时,给引用成员初始化。

#include <iostream>
using namespace std;

int global = 100;
typedef struct A
{
private:     				// 区别 5
    int a = 1;				// 区别 3
    double b;				// 不赋值则默认为 0
    int &ref = global;		// 区别 7、①

public:						// 区别 5
    void set(int n1, double n2);   
    void show();   
    struct B				// 区别 4
    {
        char c;
    };
}A;

void A::set(int n1, double n2)			// 区别 2
{
    a = n1;
    b = n2;
}
void A::show()							// 区别 2
{
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "ref = " << ref << endl;
}

int main()
{
    int var = 90;
    // A::B b1;     				// 定义了一个包含在 结构体A中的 B结构体类型的 变量
    
    A a1;               // 区别 1
    a1.show();
    a1.set(12, 9.8);   	// 调用公有函数set 给私有成员赋值
    a1.show();   		// 调用公有函数show 打印私有成员的值
    cout << sizeof(A) << endl;

    // A a2 = {12, 34, var};		// 区别 7、②
    
    return 0;
}

在这里插入图片描述

字节对齐的规则:

每一个结构体中成员都在其本身偏移量的整数倍上,

自身偏移量 = 操作系统对齐数 < 自身所占字节数 ? 操作系统对齐数 : 自身所占字节;

结构体本身的大小:最大成员偏移量的整数倍。

💡 结构体练习 1

定义一个学生的结构体,包含私有的(private)成员变量:姓名、身高,
公有的(public)成员变量:成绩。
对于私有的成员变量,提供公有的接口(方法),实现对身高和姓名的赋值,输出身高、姓名和成绩,
再实现一个函数完成对成绩的修改。定义结构体变量,在主函数内对封装的函数进行测试。

#include <iostream>
using namespace std;

typedef struct Student
{
private:
    string name;
    float height;

public:
    float score;
    void show();
    void init(string x, float y, float z);

}stu;

void stu::show()
{
    cout << "Name: " << name  << endl;
    cout << "Height: " << height  << endl;
    cout << "Score: " << score  << endl;
}

void stu::init(string x, float y, float z)
{
    name = x;
    height = y;
    score = z;
}

void modify(float z, stu &s)		// score 为公用权限,所以此函数可以定义在结构体外
{									// 需要指定 是哪个结构体变量的 score
    s.score = z;
}

int main()
{
    stu s1;
    s1.init("Li Hua", 162.9, 0);
    cout << "Before: " << endl;
    s1.show();

    modify(59.9, s1);
    cout << endl << "After: " << endl;
    s1.show();

    return 0;
}

在这里插入图片描述

💡 字节对齐练习

💡 结构体练习 2

定义一个结构体数组,给结构体数组中的各成员中的每个成员变量赋值,并对结构体数组中所有成员的成绩进行排序。

#include <iostream>
using namespace std;

int num;
struct Student
{
private:
    string name;
    float height;

public:
    float score;
    void init(string x, float y, float z);
    void mySort(Student (&ref)[3]);
    void show();
};

void Student::init(string x, float y, float z)
{
    name = x;
    height = y;
    score = z;
}

void Student::mySort(Student (&ref)[3])
{
    Student temp;
    int length = sizeof(ref) / sizeof(ref[0]);
    for (int i = 0; i < length-1; i++)
        for (int j = 0; j < length-1-i; j++)
        {
            if (ref[j].score > ref[j+1].score)	  // 只比较成绩,但交换整个结构体成员的顺序
            {
                temp = ref[j];
                ref[j] = ref[j+1];
                ref[j+1] = temp;
            }
        }
}

void Student::show()
{
    cout << "Name: " << name  << '\t';
    cout << "Height: " << height  << '\t';
    cout << "Score: " << score  << endl;
}

int main()
{
    Student s[3];
    string name;
    float height;
    float score;

    cout  << "Input \"name\", \"height\" and \"score\" in sequence:" << endl;

    for (int i = 0; i < 3; i++)
    {
        getline(cin, name);
        cin >> height >> score;
        getchar();						// 一定要在这里吞垃圾字符!!!!!!!!!!
        s[i].init(name, height, score);
    }

    s[0].mySort(s);
    for (int i = 0; i < 3; i++)
        s[i].show();

    return 0;
}

运行结果如下:
在这里插入图片描述

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值