笔记复习
1.new操作符
作用:在堆区开辟数据,由程序员手动开辟,手动释放,释放时利用操作符delete
语法:new 数据类型(new得到的是一段空间的首地址。所以一般需要用指针来存放这段地址)
应用场景:
1.为变量动态分配内存,包括基本数据类型变量int、double;一维数组;二维数组;
2.为类对象动态分配内存,也可以为结构体分配内存(类和结构体类似);
1)利用new分配一般数据类型的内存,细分下来有两种方法:
a.
#include<iostream>
using namespace std;
int main() {
int* p = new int;
*p = 10;
cout << *p << endl;
return 0;
}
细心的同学可能会注意到,此时我并没有释放内存,但程序依然能够正常运行,这也说明当出现内存泄露问题时,程序不一定会报错
b.
#include<iostream>
using namespace std;
int main() {
int* p = new int(10);
cout << *p << endl;
return 0;
}
2)为一维数组分配内存
int* array1D = new int[5];
delete[] array1D;
3)为二维数组分配内存
int(*array2D)[2] = new int[3][2];
//这里int(*array2D)[2]中的2表示每一行包含2个int类型的元素
delete[] array2D;
4)为结构体分配内存
#include <iostream>
using namespace std;
struct Person {
string name;
int age;
};
int main() {
// 动态分配一个 Person 结构体
Person* person = new Person;
// 初始化结构体成员
person->name = "Alice";
person->age = 30;
// 输出结构体成员
cout << "Name: " << person->name << ", Age: " << person->age << endl;
// 释放结构体的内存
delete person;
return 0;
}
2.引用
作用:给变量起别名
语法:数据类型&别名=原名
示例:
int a=10;
int&b=a;
此时相当于给a起了一个别名b,当访问b时实际上是访问a
注意事项:
1.引用必须初始化
当我们用一个变量b给变量a起别名时,如果a没有初始化,那么输出b时输出的是一串乱码
2.引用无法改变
当我们给a起了别名b时,无法再用b给其他变量起别名
(这里大家有没有感觉很熟悉?这和前面我们学习的指针常量很像)
3.引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参,这时候更改形参的同时也会更改实参
优点:可以简化指针修饰实参
示例:
#include<iostream>
using namespace std;
void my(int& a, int& b) {//这时候my函数中的a,b分别是main函数中a,b的别名
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
//my(10, 20);我们是给a,b起别名,而不是给10,20起别名,因此传入数据10,20直接传递数字是不行的
my(a, b);//传入变量a,b
cout << "a=" << a << endl;
cout << "b=" << b << endl;
system("pause");
return 0;
}
于利用指针修改实参的值的区别在于:
1.传参时不需要传入地址
2.在函数中调用变量是不需要用*操作符解引用
4.左值和右值
在学习引用的下一个用法前,我们先来了解一下C++中左值和右值的概念
a.左值
左值是指那些可以出现在赋值运算符左侧的表达式
- 左值的特点:
- 有持久的内存地址。
- 可以被赋值。
- 可以是变量、数组元素、对象成员等。
b.右值
右值是指哪些不能出现在赋值运算符左侧的表达式
- 右值的特点:
- 不能被赋值。
- 不能被取地址。
- 通常是临时值,例如字面量、临时对象或函数返回的值。
下面我们学习什么是右值,以此为依据判断表达式是左值还是右值
常见的右值有:
10
一个或一串单纯的数字字符就是右值
int add(int a, int b) {
return a + b;
}
函数的返回值也是右值
5.引用作为函数的返回值
特点:函数的调用可以作为左值
语法:在定义函数的时候,函数类型不能是void,且需要用取址符定义;在接收函数返回值时用取址符接收。
示例:
#include<iostream>
using namespace std;
int& test02() {
static int a = 10;
//后面会解释为什么这里要定义一个静态变量
return a;
}
int main() {
int& ref2 = test02();
cout << "ref2=" << ref2 << endl;
//如果函数的返回值是引用,这个函数调用可以作为左值(粗略理解为变量)
test02() = 1000;//test02函数调用时返回a,故这行代码本质上是a=1000
cout << "ref2=" << ref2 << endl;
system("pause");
return 0;
}
(引用的值竟然是可以改变的,引用是不是就是指针常量呢?)
注意事项:不要返回局部变量的引用
前面在学习内存四区模型时我们知道,局部变量指的是在函数体中的变量,存放在栈区,这类变量在函数执行时系统为其分配内存,在函数执行后系统释放其内存,因此当我们返回他的引用时,那么这个引用在函数返回后将指向一个已经释放的内存区域,因此会报错。故我们需要在前面加上static将变量变为常量,这样该变量就会存储在常量区中,即使函数执行结束也不会被释放,当我们程序结束时才会被释放。
下面是一个返回局部变量的例子:
#include<iostream>
using namespace std;
int& test01() {
int a = 10;//局部变量存放在栈区
return a;
}
int main() {
int &ref = test01();
cout << "ref=" << ref << endl;
cout << "ref=" << ref << endl;
system("pause");
return 0;
}
这里当我们输出第一个ref时可以正常输出,这是因为编译器帮我们记住了a,但当我们输出第二个的时候就会输出乱码。
6.引用的本质
本质:引用的本质在c++内部实现时一个指针常量
下面是一个代码示例:
#include<iostream>
using namespace std;
//发现是引用,转换为int*const ref=&a;
void my(int& ref) {
ref = 100;//ref是引用,转换为*ref=100
}
int main() {
int a = 10;
int& ref = a;//自动转换为int*const ref=&a;指针常量是指指针指向不可改,也说明为什么引用不可更改
ref = 20;
cout << "a=" << a << endl;
cout << "ref=" << ref << endl;
my(a);
system("pause");
return 0;
}
但引用与指针常量也有区别,两者的区别在于指针常量使用变量名时输出的是地址,不是值。
7.常量引用
前面我们讲引用实质上是指针常量,指针的指向无法改变,指针指向的值可以改变,这时候我们可以加上const,使得指针指向的值也无法改变以防止误操作。
代码示例:
#include<iostream>
using namespace std;
void my(const int& val) {//在参数列表中在前面引用前加上const
//val = 1000;//当出现修改操作时会报错
cout << "val=" << val << endl;
}
int main() {
int a = 100;
my(a);
system("pause");
return 0;
}
前面我们说需要先初始化一个变量才可以用引用给这个变量起别名,但使用const关键字之后我们可以跳过这一步骤
int& ref = 10;//直接这样引用是错误的,前面我们讲引用是给变量起别名,那么应该先有名才能起别名
const int& ref = 10;//需要在前面加上const
//加上const后,编译器将代码修改为 int temp = 10; const int& ref = temp;
这样有什么用呢?这时候我们可以反过来理解,即ref是一个指向10的变量,后面可以不需要用到取址符给变量取别名
#include<iostream>
using namespace std;
int main() {
const int& ref = 10;
int c = ref;
int b = ref;
system("pause");
return 0;
}