C++学习笔记之指针的使用(2)

1️⃣指针的基本概念

首先讲讲指针的作用:可以通过指针间接地访问内存。
我们可以利用指针变量来保存地址。

2️⃣指针的定义和使用

指针变量定义语法:数据类型 * 变量名;
指针变量为地址,要输出指针变量地址存储的值要加上*
&:取地址符;*:取值符。
代码举例:

int main()
{
    int a = 18;
    // 定义指针变量p
    int* p;
    // 把a的地址给变量p
    p = &a;
    cout << p << endl;
    cout << &a << endl;
    // 解引用
    cout << *p;
    return 0;
}

在这里插入图片描述
指针变量和普通变量的区别:
普通变量存放的是数据,指针变量存放的是地址。
指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用。
总结:
我们可以通过 & 符号 获取变量的地址。
利用指针可以记录地址。
对指针变量解引用,可以操作指针指向的内存数据。
在这里插入图片描述
在这里插入图片描述
如何分配指针地址:
在这里插入图片描述

3️⃣指针所占内存空间

指针也是一种数据类型,那么这种数据类型占用多少内存空间呢?
代码演示:

int main()
{
	// 在64位系统演示下,输出结果都是8位
    cout << sizeof(int*) << endl;
    cout << sizeof(float*) << endl;
    cout << sizeof(double*) << endl;
    cout << sizeof(char*) << endl;
    return 0;
}

注意:在32位系统下输出结果都是4位。

4️⃣空指针和野指针

什么是空指针?空指针的指针变量指向内存中编号为0的空间,它的作用是用来初始化指针变量;空指针指向的内存是不可以访问的。
代码演示:

int main() {
	//指针变量p指向内存地址编号为0的空间
	int * p = NULL;
	//访问空指针报错 
	//内存编号0 ~255为系统占用内存,不允许用户访问
	cout << *p << endl;
	return 0;
}

什么是野指针?野指针就是指针变量指向非法的内存空间。
代码演示:

int main() {
	//指针变量p指向内存地址编号为0x1100的空间
	int * p = (int *)0x1100;
	//访问野指针报错 
	cout << *p << endl;
	return 0;
}

空指针和野指针都不是我们申请的空间,因此不要访问。

5️⃣const修饰指针

const修饰指针有三种情况:
const修饰指针——常量指针
const修饰常量——指针常量
const即修饰指针,又修饰常量
代码演示:

int main() {
	int a = 10;
	int b = 10;
	//const修饰的是指针,指针指向可以改,指针指向的值不可以更改
	const int * p1 = &a; 
	p1 = &b; //正确
	//*p1 = 100;  报错
	//const修饰的是常量,指针指向不可以改,指针指向的值可以更改
	int * const p2 = &a;
	//p2 = &b; //错误
	*p2 = 100; //正确
    //const既修饰指针又修饰常量,都不可以改
	const int * const p3 = &a;
	//p3 = &b; //错误
	//*p3 = 100; //错误
	return 0;
}

6️⃣new,delete分配,释放内存,必须成对使用(不要用野指针)
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。其中new可以替代malloc的功能,delete可以替代free的功能。
1.基本语法
c++动态内存的基本语法如下:

void Test()
{
	// 动态申请一个int类型大小的空间
	int* p1 = new int;
	// 动态申请一个int类型的空间并初始化为10
	int* p2 = new int(10);
	// 动态申请10个int类型的空间,动态数组
	int* p3 = new int[10];
    // 动态申请3个int类型的空间并初始化
    int* p4 = new int[3]{1,2,3}; 
	
    //释放空间
    delete p1;
	delete p2;
	delete[] p3;
    delete[] p4;
}

可以看出,和c语言相比,c++的语法更加简洁。申请空间时不必再进行指针的强制类型转换,编译器会直接为我们完成,并且增加了初始化功能。

注意:当申请多块空间时(比如p3申请了十个int类型空间),且对象是自定义类型时,释放时在指针前面必须加一个[ ],否则会发生内存泄漏(这里的内存泄露指的是自定义类型的析构函数只会调用一次),编译时没有问题,但是运行时会发生崩溃。如果对象是内置类型的话可以不加[ ],但不建议这样使用。
在这里插入图片描述
在这里插入图片描述
动态数组的使用:
在这里插入图片描述
在这里插入图片描述

7️⃣一维数组的地址

【以整型一维数组int arr[n]为例】
(1) arr 等价于 &arr[0]
表示数组首元素地址,指向数组第1个元素,arr + 1或&arr[0] + 1会跳过第1个元素【加上1个数组元素的字节数】,指向数组的下1个元素。
arr或&arr[0]的地址类型为int *类型,使用int类型的指针(指向数组首元素的指针)接收。
(2) &arr
表示整个数组的地址,指向整个数组,&arr + 1会跳过整个数组【加上整个数组的总字节数】,如int *p = (int )(&arr + 1),指针p指向数组的末尾。
&arr的地址类型为int (
)[数组长度]类型,使用数组指针(指向数组的指针)接收。
示例:

#include <iostream>
using namespace std;

int main() {
	//一维数组
	int arr[5] = { 1,2,3,4,5 };

	/* 一维数组的地址与指针 */
	int* p1 = (int *)(&arr + 1);	//&arr:整个数组的地址	//&arr + 1:指向数组的末尾处
	int* p2 = (int*)(arr + 1);		//arr等价于&arr[0],类型为int *类型:数组首元素地址 
	cout << p1[-2] << endl;		//4
	cout << *p2 << endl;		//2


	cout << arr << endl;			//009DFBB8
	cout << *arr << endl;			//1【第1个元素值】
	cout << arr + 1 << endl;		//009DFBBC	后移4字节【跳过1个元素】
	cout << *(arr + 1) << endl;		//2【第2个元素值】
		
	cout << &arr[0] << endl;		//009DFBB8
	cout << *(&arr[0]) << endl;		//1【第1个元素值】
	cout << &arr[0] + 1 << endl;	//009DFBBC	后移4字节【跳过1个元素】
	cout << *(&arr[0] + 1) << endl;	//2【第2个元素值】

	cout << &arr << endl;			//009DFBB8
	cout << *(&arr) << endl;		//009DFBB8
	cout << &arr + 1 << endl;		//009DFBCC	后移4*5=20字节【跳过整个数组】
	cout << *(&arr + 1) << endl;	//009DFBCC
	
	return 0;
}

第二段的第二行一个是&tell+1不是&tell+2:
在这里插入图片描述

8️⃣指针和字符串

在这里插入图片描述
要用cin对指针赋值,该指针必须有初值,可以用指针指向数组地址然后再进行赋值
注意:对数组赋值字符串的时候应该使用:

strcpy();

或者

strncpy();

例如:

char a[20]="asdasda";
char*p;
p=new char[strlen(a)+1];
strcpy(p,a);

9️⃣new创建动态结构,结构体指针

在运行时创建数组优于在编译时创建数组,对于结构也是如此。需要在程序运行时为结构分配所需的空间,这也可以使用new运算符来完成。通过使用new,可以创建动态结构。同样,“动态”意味着内存是在运行时,而不是编译时分配的。由于类与结构非常相似,因此木节介绍的有关结构的技术也适用于类。
将new用于结构由两步组成:创建结构和访问其成员。要创建结构,需要同时使用结构类型和new.例如,要创建一个未命名的inflatable类型,并将其地址赋给一个指针,可以这样做:
inflatable * ps = new inflatable;
这将把足以存储inflatable结构的一块可用内存的地址赋给ps。这种句法和C++的内置类型完全相同。
比较棘手的一步是访问成员。创建动态结构时,不能将成员运算符句点用于结构名,因为这种结构没有名称,只是知道它的地址。C++专门为这种情况提供了一个运算符:箭头成员运算符(->)。该运算符由连字符和大于号组成,可用于指向结构的指针,就像点运算符可用于结构名一样。例如,如果ps指向一个inflatable结构,则ps->price是被指向的结构的price成员。

提示,有时,C++新手在指定结构成员时,搞不清楚何时应使用句点运算符,何时应使用箭头运算符。规则非常简单。如果结构标识符是结构名,则使用据点运算符;如果标识符是指向结构的指针,则使用箭头运算符。

另一种访问结构成员的方法是,如果ps是指向结构的指针,则ps就是被指向的值——结构本身。由于ps是一个结构,因此(*ps).price是该结构的price成员。C++的运算符优先规则要求使用括号。

程序4.21使用new创建一个未命名的结构,并演示了两种访问结构成员的指针表示法。

程序4.21 newstrct.cpp

//newstrct.cpp——using new with a structure
#include<iostream>
using namespace std;
struct inflatable
{
	char name[20];
	float volume;
	double price;
 } ;
int main()
{
	inflatable * ps = new inflatable;
	cout<<"Enter name of inflatable item : ";
	cin.get(ps->name,20);
	cout<<"Enter volume in cubic feet: ";
	cin>>(*ps).volume; //简单来说就是对象是值就用点运算符
	cout<<"Enter price: $";
	cin>>ps->price; //对象是地址就用->运算符
	cout<<"Name : "<<(*ps).name<<endl;
	cout<<"Volume : "<<ps->volume<<" cubic feet\n";
	cout<<"Price : $"<<ps->price<<endl;
	delete ps;
	return 0;
}

在这里插入图片描述

1️⃣0️⃣getname()创建动态数组函数,返回数组地址(需要自己写)

该函数返回一个指向输入字符串的指针。该函数将输入读入到一个大型的临时数组中,然后使用new[]创建一个刚好能够存储该输入字符串的内存块,并返回一个指向该内存块的指针。对于读取大量字符串的程序,这种方法可以节省大量内存(实际编写程序时,使用string类将更容易,因为这样可以使用内置的new和delete)。
举例:

//detete.cpp——using the delete operator
#include<iostream>
#include<cstring>
using namespace std;
char * getname(void);
int main()
{
	char * name;
	name = getname();
	cout<<name<<" at "<<(int *)name<<endl;
	delete [] name ;
	name = getname();
	cout<<name<<" at "<<(int *)name<<endl;
	delete [] name ;
	return 0;
}
char * getname()
{
	char temp[80];
	cout<<"Enter last name: ";
	cin>>temp;
	char * pn = new char[strlen(temp)+1];
	strcpy(pn,temp);
	return pn; 
}

在这里插入图片描述
程序说明
来看下程序4.22中的函数getname(),它使用cin将输入的单词放temp数组中,然后使用new分配新内存,以存保该单间。程序需要strlen(temp)+ 1个字符(包括空字符)来存储该字符串,因此将这个值提供给new.获得空间后,gehamet )使用标准库函数strcpy( )将temp中的字符串复制到新的内存块中。该函数并不检查内存块是否能够容纳字符串,但getname()通过使用new请求合适的字节数来完成了这样的工作,最后,函数返回p,这是学符审副本的地址。
在main()中,返回值(地址)嫩被赋给指针name。该指针是在main()中定义的,但它指向getname()函数中分配的内存块。然后,程序打印该字符串及其地址。
接下来,在释故name指向的内存块后,main再次调用getname()。C++不保证新释放的内存就是下依次使用new时选择的内存,从程序运行结果可知。确实不是。

在这个例子中,getname()分配内存,而main()释放内存。将new和delete放在不同的函数中通常并不好似个好办法,因为这样很容易忘记使用delete。不过这个例子确实吧new和delete分开放置了,只是为了说明这种做也是可以的。

为了解该程序的一些更为微妙的方面,需要知道一些有关C++是如何处理内存的知识。下面介绍一些这样的知识,这些知识将在第9章做全面介绍。
在这里插入图片描述
在这里插入图片描述

1️⃣1️⃣.点运算符和->运算符

如果要操作的值是变量名就用点运算符,要操作的是地址,就用->运算符
在这里插入图片描述

1️⃣2️⃣类型组合,前面知识点总结

本章介绍了数组、结构和指针。可以各种方式组合它们,下面介绍其中的一些,从结构开始:

struct antarctica_years_end

{

int years;

};

可以创建这种类型的变量:

antarctica_years_end s01,s02,s03;

然后使用成员运算符访问其成员:

s01.year = 1998;

可创建指向这种结构的指针:

antarctica_years_end * pa = &s02;

将该指针设置为有效地址后,就可使用间接成员运算符来访问成员:

pa->year = 1999;

可创建结构数组:

antarctica_years_end trio[3];

然后,可以使用成员运算符访问元素的成员:

trio[0].year = 2003;

其中trio是一个数组,trio[0]是一个结构,而trio[0].year是该结构的一个成员。由于数组名是一个指针,因此也可使用间接运算符:

(trio + 1) -> year = 2004;

可创建指针数组:

const antarctica_years_end * arp[3] = {&s01,&s02.&s03};
乍一看,这有点复杂。如果使用该数组来访问数据呢?既然arp是一个指针数组,arp[1]就是一个指针,可将间接成员运算符应用于它,以访问成员:

std::cout<<arp[1]->year<<std::endl;

可创建指向上述数组的指针:

const antarctica_years_end ** ppa = arp;

其中arp是一个数组的名称,因此它是第一个元素的地址。但其第一个元素为指针,因此ppa是一个指针,指向一个指向const antarctica_years_end的指针。这种声明很容易容错。例如,您可能遗漏const,忘记*,搞错顺序或结构类型。下面的示例演示了C++11版本的auto提供的方便。编译器知道arp的类型,能够正确地推断出ppb的类型:

auto ppd = arp ;

在以前,编译器利用它推断的类型来指出声明错误,而现在,您可利用它的这种推断能力。

如何使用ppa来访问数据呢?由于ppa是一个指向结构指针的指针,因此*ppa是一个结构指针,可将间接成员运算符应用于它:

std::cout<<(*ppa)->year<<std::endl;

std::cout<<(*(ppb+1))->year<<std::endl;

由于ppa指向arp的第一个元素,因此*ppa为第一个元素,即&s01,所以,(*ppa)->year为s01的year成员。在第二条语句中,ppb+1指向下一个元素arp[1],即&s02。其中的括号必不可少,这样才能正确地结合。例如,ppa->year试图将运算符应用于ppa->year,这将导致错误,因为成员year不是指针。

上面所有的说法都对吗?程序4.23将这些语句放到了一个简短的程序中。

程序4.23 mixtypes.cpp

//mixtypes.cpp——some type combinations
#include<iostream>
 
struct antarctica_years_end
{
	int year;
};
int main()
{	antarctica_years_end s01,s02,s03;
	s01.year = 1998;
	antarctica_years_end * pa=&s02;
	pa->year = 1999;
	antarctica_years_end trio[3];
	trio[0].year = 2003;
	std::cout<<trio->year <<std::endl;
	const antarctica_years_end *arp[3] = {&s01,&s02,&s03};
	std::cout<<arp[1]->year<<std::endl;
	const antarctica_years_end **ppa = arp;
	auto ppb = arp;//C++11才可以使用,楼主的电脑报错了 
	std::cout<<(*ppa)->year<<std::endl;
	std::cout<<(*(ppb+1))->year<<std::endl;
	
	return 0;
}

输出:
2003
1999
1998
1999

1️⃣3️⃣

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值