目录
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️⃣