引用
定义和初始化
**数据类型 &引用名 = 目标名;**
引用和目标共用同一片空间(相当于对一片空间取别名)。
引用的底层实现:数据类型 * 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;
}
运行结果如下: