New关键字、引用&与指针的学习记录


一、New关键字的学习

1.C++通过new关键字进行动态分配内存。
2.new开辟的空间存储在堆上,而我们定义的变量存储在栈上。
3.new分配的空间使用delete释放,new[]使用delete[]释放。

Int* pi = new int(5); //表示动态分配一个int,初始化为5
Int* pa = new int[5]; //表示动态分配一个数组,数组大小为5

若定义了一个类A,类成员有 int i;
构造函数为:A::A(int _i):i(_i*_i);

A* pa = new A(3);
共做了三件事:
1.获得一块内存空间,空间大小为sizeof(A);
2.调用构造函数;
3.返回指针pa

二、&的学习

//1.取地址,int型指针b的值为a的地址
int a = 1; int* b = &a;
//2.引用,b是a的别名
int a = 1;int &b = a;

三、指针的学习

1.指针是什么?

指针是“指向”另外一种类型的复合类型。复合类型是指基于其他类型定义的类型。
理解指针首先从内存说起,内存是一个很大的,线性的字节数组。每一个字节都是固定的大小,由8个二进制位组成。最关键的是,每一个字节都有一个唯一的编号,编号从0开始,一直到最后一个字节。程序加载到内存中后,在程序中使用的变量、常量、函数等数据都有自己唯一一个的编号,这个编号就是这个数据的地址。

指针的值实质是内存单元(即字节)的编号,所以指针单独从数值上看,也是整数,他们一般使用16进制表示。指针的值使用一个机器字的大小来存储,也就是说,对于一个机器字为w位的电脑而言,它的虚拟地址空间是0~2(w次幂)-1,程序最多能够访问2的w次幂个字节,这就是为什么xp这种32位系统最大支持4GB内存的原因。

因此可以理解为:指针是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量。

2.变量在程序中的储存

举一个最简单的例子int a = 1,假设计算机使用大端方式存储:
在这里插入图片描述
内存数据有几点规律:
①计算机中的所有数据都是以二进制进行存储的
②数据类型决定了占用内存的大小
③占据内存的地址就是地址值最小的那个字节的地址。

现在可以理解a在内存中为什么占用4个字节,而且首地址为0028FF40了。

3.指针对象(变量)

用来保存指针的对象,就是指针对象。
如果指针变量p1保存了变量a的地址,则就是说:p1指向了变量a,也可以说p1指向了a所在的内存块,这种指向关系,在图中一般用箭头表示:
在这里插入图片描述
指针对象p1也有自己的内存空间,32位机器占4个字节,64位机器占用8个字节。

3.1定义指针对象

定义指针变量时,在变量名前写一个*号,这个变量就变成了对应变量类型的指针变量。
必要时要加()来避免优先级的问题:

Int* p_int ;  //指向int类型的变量指针
double* p_double;  //指向double类型的指针
Student* p_stuct;  //类或结构体类型的指针
Int** p_pointer;  //指向一个整形变量指针的指针
Int(*p_aar)[3];  //指向含有3个int元素的数组的指针
Int(*p_func)(int,int); //指向返回类型为int,有2个int形参的函数的指针

其中,几种基本的数据类型再进行简单的介绍
布尔型,即bool,它的取值只能为true或者false,分别代表非零与零。对布尔型的赋值可以直接用true或者false进行赋值。
字符型,即char,它是基本的字符类型,一个char的空间应确保可以存放任意字符对应的数字值。
整型
在这里插入图片描述
浮点型,即float数据类型,被认为是单精度,double数据类型通常是float的两倍大小,因此被认为是双精度。顾名思义,long double数据类型要比double要大。这些数据类型的确切大小取决于当前使用的计算机。可以唯一保证的是:
Double 至少与 float 一样大
Long double 至少与double 一样大

对于float数来说有效数字约为7位,所以整数部分占的位数越多,小数部分的精度就会越低,当整数部分超过9999999后小数部分已经完全无精度了。
而我们有时候采用float64,它在一个内存中占用8个字节,可以有效提高精度。

3.2获取对象地址

指针用于存放某个对象的地址,要想获取该地址,需要使用取地址符(&),如下:

int add(int a,int b)
{
    
    
return a+b;
}

int main(void)
{
    
    
int num = 97;
float score = 10.00F;
int arr[3] = {
    
    1,2,3};

int* p_num = #
int* p_arr1 = arr; //p_arr1意思是指向数组的第一个元素
float* p_score = &score;
int (*p_arr)[3] = &arr;
int (*fp_add)(int, int) = add;
const char* p_msg = "Hi";
return 0;
}

通过上面可以看到&的使用,但是有几个例子没有使用&,因为这是特殊情况:
①数组名的值就是这个数组的第一个元素的地址
②函数名的值就是这个函数的地址
③字符串字面值常量作为右值时,就是这个字符串对应的字符数组的名称,也就是这个字符串在内存中的地址。

3.3解析地址对象

如果指针指向了一个对象,则允许使用解引用符(*)来访问该对象,如下:

#include <iostream>
int main()
{
    
    
int age = 19;
int* p_age = &age;
*p_age = 20;  //通过指针修改指向的内存数据

std::cout<<"age = "<<*p_age<<"\n";  //通过指针读取指向的内存数据
std::cout<<"age = "<<age<<std::endl;

return 0;
}

对于结构体和类,两者的差别很小,所以几乎可以等同,则使用->符号访问内部成员:

struct Student
{
    
    
	char name[31];
	int age;
	float score;
};

int main()
{
    
    
	Student stu = {
    
    "Bob", 19, 98.0};
	Student* p_stu = &stu;

	p_stu->age = 20;
	p_stu->score = 99.0;
	std::cout<<"name"<<p_stu->name<<"age"<<p_stu->age<<"score"<<p_stu->score
	<<std::endl;
	return 0;
}

3.4指针值的状态

指针的值(即地址)总会是下列四种状态之一:
①指向一个对象的地址
②指向紧邻对象所占空间的下一个位置
③空指针,意味着指针没有指向任何对象
④无效指针,上述情况之外的其他值

3.5指针之间的赋值

指针赋值和int变量赋值一样,就是将地址的值拷贝给另外一个。指针之间的赋值是一种浅拷贝,就是在多个编程单元之间共享内存数据的高效的方法。

Int* p1 = &a;
Int* p2 = p1;

4.指针内含信息

通过上面介绍,我们可以看出指针包含两部分信息:所指向的值和类型信息。

5.函数和指针

5.1函数的参数和指针

实参传递给形参,是按值传递的,也就是说,函数中的形参是实参的拷贝份,形参和实参只是在值上面一样,而不是同一个内存数据对象。
这就意味着:这种数据传递是单向的,即从调用者传递给被调函数,而被调函数无法修改传递的参数达到回传的效果。

#include <iostream>

void change(int a)
{
    
    
	a++;  //在函数中改变的只是这个函数的局部变量a,而随着函数执行结束,a被销毁。
}

int main()
{
    
    
	int age = 19;
	change(age);
	std::cout<<"age = "<<age;  //age = 19
	return 0;
}

输出结果为age = 19,说明被调函数无法修改传递的参数达到回传的效果

有时候我们可以使用函数的返回值来回传数据,在简单的情况下是可以的,但是如果返回值有其他用途(例如返回函数的执行状态量),或者回传的数据不止一个,返回值就解决不了了。

传递变量的指针可以轻松解决上述问题

#include <iostream>

void change(int* a)
{
    
    
	(*a)++;  //因为传递的是age的地址,因此a指向内存数据age
	//当在函数中对指针a进行解地址时,会直接去内存中找到age这个数据,然后将它增1
	
}

int main()
{
    
    
	int age = 19;
	change(&age);
	std::cout<<"age = "<<age;  //age = 20
	return 0;
}

这样返回值就是age = 20

除了上述方法,还可以选择使用引用:

void change(int &a)
{
    
    
	a++;  //a就是age的别名,或者也可以说外号,所以对a进行加1同样就是对age
//加1
}

int main()
{
    
    
	int age = 19;
	change(age);
	std::cout<<"age = "<<age;  //age = 20
	return 0;
}

5.2函数的指针

每一个函数本身也是一种程序数据,一个函数包含了多条执行语句,它被编译后,实质上是多条机器指令的合集。在程序载入到内存之后,函数的机器指令存放在一个特定的逻辑区域:代码区。既然是存放在内存中,那么函数也有自己的指针。

其实函数名单独使用时就是这个函数的指针。

#include <iostream>

int add(int a, int b) //函数的定义
{
    
    
	return a+b;
}

int main()
{
    
    
int (*p_add)(int, int);  //函数指针的声明

p_add = add;   //给函数指针赋值
p_add = &add;  //跟上面是一样的

int c = p_add(1,2);  //跟函数名一样使用
int d = (*p_add)(1,2);  //跟上面的调用是一样的

std::cout<<"The value of c"<<c<<std::endl;
std::cout<<"The value of d"<<d<<std::endl;

return 0;
}

5.3返回值和指针

这里唯一需要注意的是不要把非静态局部变量的地址返回。我们知道局部变量是在栈中的,由系统创建和销毁,返回之后的地址可能无效,这样会造成bug。
可以返回全局变量、静态的局部变量、动态内存等的地址返回。

6.const与指针

这里主要就是指针常量和常量指针,两者主要的区别是看const修饰的谁。

6.1常量指针

实际是个指针,指针本身是个常量。

Int a = 97;
Int b = 98;
Int* const p = &a;
*p = 98; //正确
P = &b;  //错误

常量指针必须初始化,而且一旦初始化完成,则它的值就不能改变了。

6.2指向常量的指针

Int a = 97;
Int b = 98;
Const int* p = &a;
Int const *p = &a;  //两者的含义是一样的
*p = 98;  //编译出错
P = &b;  //正确

所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,但是对象的值可以通过其他途径进行改变。

小结

今天又是摸鱼的一天…周一快乐[爱心]!

猜你喜欢

转载自blog.csdn.net/weixin_46181372/article/details/109579777