第六章 关于指针一些自己的理解
最重要:数组名与指针同为指向地址,若一个指针指向了数组名,则可以使用指针名+[下标]进行数组数据访问。
其次:如果地址确定!则加*解引用符,实现的就是对数据的引用操作,如果地址不确定,加引用符*就是对该变量的首地址取地址操作!!!啥叫地址不确定?简而言之你说的地址,是一整行的首地址,那你的继续操作,就单单只是对地址的操作了,比如*a[0]+1首地址和a[0]+1是等价的。
在操作连续的存储空间的时候,在一定的程度上指针与数组是等价的;指针自加自减必须对连续的存储空间进行的操作。
指针的类型:
例如int *a; 它的类型为int *;表示该指针是一个能够指向整形数据的一个指针类型。
意义:1.指针类型决定了指针进行解引用操作的时候,能访问空间的大小。
2.指针类型决定了指针的步长(指针走一步可以走多远)。
指针的指向类型:
含义:当你通过指针去访问一个内存区或者变量的存储地址时,指针的指向类型就代表了你能够访问的哪种类型。
若二者类型不一致,则编译器会报错:无法将两种类型不一致的指针相互转换如下图
指针变量:
使用规则:
定义:类型名 *指针变量名;
注意:指针变量存储的时地址!而非一个数据,指针变量自己有一个地址,但是与指针变量所指向的地址含义不同。
使用规则:
首先我们介绍两个运算符 * 和 &。
先介绍&:术语:取地址运算符
顾名思义,取地址运算符,肯定是取地址的嘛,我们的计算机会自动的将内存分为多少个地址,每定义一个变量,我们的编译器就会自动的为这个变量找一个合适的内存空间储存起来,简要的理解为,每个变量对应一个地址。
我们知道,指针变量里面存储的是地址,所以地址的使用规则会在介绍完*的时候会有更深一步的理解,现在简单理解为 & + 一个变量名,就是从计算机内部,把你存储这个变量的地址给我扣出来了,而我们的指针变量就可以把你这个地址给存起来了。
再介绍*:术语:指针运算符(又名间接寻址符)(想不到吧我还叫 解引用操作符)
寻址,找啥?地址哇,找到了这个地址,我就可以对这个地址里面存储的变量进行操做(俗话就是我单单一个指针变量,即使已经存储了地址,但是我没有办法去操做你,因为我只有你的地址,你不给我命令让我进去你的地址里面,而*的作用就是我撬开你地址大门的钥匙,我能进去操作你了)。
在上面我们留了一个小小的疑问:& 怎么与 *一起使用呢?
如下代码:
#include <iostream>
using namespace std;
int main ()
{int a;//定义了普通整形变量a
a=1;//给a赋值为1;
cout<<a;//输出一下a
int *b;//定义了指针变量b
b=&a;//先把a的地址找到了,赋值给了指针变量b
*b=100;//指针变量b存储了a的地址,*打开了a的大门对a赋值为100
cout<<b<<” ”<<a<<endl;
return 0;
}
从上面代码我们可以看出:
& *都是从右边结合的 啥意思?
例如*&b;表示先取了b的地址,然后对找地址;现在若对它进行操作就是对普通变量b进行操作;相反的&*a;表示啥?(一定要注意此时的a是一个指针变量若不是,一定报错)。
指针变量的初始化:
变量还是变量;依然是定义、使用规则和初始化三个部分。
指针变量存储的是地址,如果你给他初始化一个其他的数字啥的,就会返回错误,编译器可能认为你没有出错,因为你确实定义了变量,进行了初始化,在编译期间没有错。
指针的大小:
指针的大小并不是和int char double 等定义的一样而是由各自的系统所决定的。指针变量存储的是地址,地址是啥?是你操作系统的位数。
比如64位的系统,就是由64个bit,也就是说一个地址有64个二进制数字,而一个指针变量存储的就是一个地址的大小;测试代码也很简单,如下;
#include <iostream>
Using namespace std;
Int main ()
{
int *a;
int b;
a=&b;
cout<<sizeof(a);
}
8个bit一个字节,所以在64位操作系统中,一个指针变量就是一个字节。
指针与整数相加减运算:
从数组方面来迅速理解;代码如下;
#include <iostream>
using namespace std;
int main()
{
int a[5]={0};
int *b;
b=&a[0];
for(int i=0;i!=5;++i)
{
cout<<&a[i]<<" ";
}
cout<<endl;
cout<<b<<" ";
cout<<b+1<<" ";
cout<<b+2<<" ";
cout<<b+3<<" ";
cout<<b+4<<" ";
}
我们会发现,每次加一,指针变量存储的地址就会向后拓展一个单位,也就是4;并且与数组的关系是严格的一一对应,验证了我们以前讲的数组是一个连续的存储空间,同时又证明了指针变量加(减),是在向内存的高一单元(减一单元)的方向进行的,此时的单元长度,与定义的数据类型长度是一致的。
指针的关系运算:
我们普通变量里有“==”“>“”<“等关系运算符,同样的指针变量也有。
Int *a,*b;
a=&某个变量;
b=&某个变量。
//a==b;表示a是否与b的存储的地址一致,
//a>b;表示a的存储地址的单位是否比b存储的单位大。
//a==0||a==NULL;表示a是否为空地址(没有进行初始化).
下面介绍数组指针and指针数组
数组指针与指针数组
数组指针指的是:数组的指针 是一个指针,是一个指向数组的指针。 指针数组指的是: 指针的数组 是一个数组,存放着指针的一个数组。
指针数组:
定义:int *a[5];
解释:由于[]的运算级比*大,所以a先和[5]结合,构成一个数组而 “int*”修饰这个数组类型,int*是指针的类型,表示 a[5]里面每一个都是指针,这就构成了一个指针数组。
代码验证如下:
#include <iostream>
using namespace std;
int main ()
{
int *a[5];
int b[5];
a[1]=&b[1];
a[2]=&b[2];
a[3]=&b[3];
a[4]=&b[4];
a[0]=&b[0];
for (int i=0;i<5;i++)
{
cin>>*a[i];
}
for (int i=0;i<5;i++)
cout<<b[i]<<" ";
}
数组指针:
是指针-指向着数组的指针!
怎么理解?
定义:int (*p)[5];
解释:()运算级更高,*p表示p是一个指针,而int修饰[5],表示是一个数组,指针变量p指向的是数组的首地址,此时int[5],这个数组就是一个匿名数组,没有标识名的一个数组。
此时可以简单的写成int[5](*p),是一个匿名数组,p是指针变量(想想大小是多少?),里面存储了这个匿名数组的首地址。数组指针里的每个匿名元素可以放入一个数组名(也就是每个数组的首地址).
使用规则:
Int(*p)[5]; 括号把标识名和指针扩起来。
字符指针:
C语言本身并不提供string类型,其对字符串的操作是通过字符数组或者字符指针进行的。
复习:字符数组
char a[]=”blabla”;
cout<<a<<endl;
a在这里就可以当作a数的首地址对a进行的操作。
Char a [];
cin>>a;
getline(cin,a)
cin.getline(a,1000);
二者区别:
Cin不读空格,读到空格自动添加‘\0’,结束输入;
Cin.getline( )函数与getline(cin,a);函数二者可以读取空格;
尤其注意cin与cin.getline等string类函数无法混用,原因是cin之后就会有’\0’,想解决的方法就是清除缓存区,clear啥的,自己搜一下。
还有字符串长度strlen(s);
还有字符的复制strcpy(a,b);
还有字符串的连接strcat(a,b);
注意:上述函数实现的头文件均包含于string.h中。
字符指针
使用规则:
char *a=“bitch u bitch!”;
解释说明:这里定义了一个字符指针,可以将字符指针的变量名a当作字符串数组的首项(或者是首地址);字符串数组无法进行相加减,但是指针是可以进行加减的,通过指针的自加自减,是可以循环的给指针变量进行赋值的。进一步也就实现了字符指针来代替字符数组进行字符串的操作。
尤其注意:旧版标准是可以允许上面的char *a=“bitch u bitch!”;但是新版规则改啦!它是不允许将一个字符串常量转换为指针类型的如下图
字符指针当然很牛逼还有下面几种用法:
- 指向单个字符
(const)char a=’n’;
char* b=&a;
*b =’a’;
想一下加了const有啥作用?
- 指向字符数组
char str[]=”nononono”;
char *p=str;
cout<<str<<” ”<<*p<<endl;
*p=”aaaa”;
cout<<str<<” ”<<*p<<endl;
看起来这个字符数组可以改变,但是看下面的运行结果
还是不行,为啥?字符串数组,一出来就是个常量了哇!
所以这里的字符指针无法对字符串数组进行修改!和那个const一模一样。
情况2:
#include<stdio.h>
int main()
{
char *ch = "hello, world";
printf("%c\n", *ch);
printf("%s\n", ch);
return 0;
}
发现打印出来的第一个为h
第二个才为字符串
为啥?这里其实是将h的首地址送给了ch;而第二个输出相当于是将整个字符串进行了输出,而单单输出*ch仅仅是解引用了第一个h的地址,输出的也就是h。
误区3:
看代码如下:
#include <iostream>
Using namespace std;
Int main ()
{
Char a=”abcdef”;
Char b=”abcdef”;
Char *c=a;
Char *d=b;
If(a==b)
{Cout<<”haha”<<end;}
Else
{
Cout<<”hehe”<<endl;
}
Return 0;
}
结果输出为hehe,想都不用想,a和b的首地址肯定不一样哇。
#include <iostream>
Using namespace std;
Int main ()
{
Char a=”abcdef”;
Char b=”abcdef”;
Char *c=a;
Char *d=b;
If(d==c)
{Cout<<”haha”<<end;}
Else
{
Cout<<”hehe”<<endl;
}
Return 0;
}
改一下条件,这个样子输出呢?肯定有人说那还是hehe哇,nononno小伙子,因为你指针指的是同一个常量表达式,所以你的输出应该是haha,编译器自己给你把这两个指针放到了一起类似于下图。
abcdef |
c d
为什么讲指针是C系语言的精华?
1 指针作用是“传引用”,不是“传值”。java、C++等语言都有引用,强大在类,这是语言特性。
2 指针能够移动,能够取值,这是指针的精髓,C语言因此强大。
3 如果没有指针,C程序将没有任何的"灵活性"可言,子函数封装性约等于0。
二维数组:
行指针与列指针
我们知道 一个二维的数组是由行和列组成的 如下列内存图
A[0][0] |
A[0][1] |
A[0][2] |
A[1][0] |
A[1][1] |
A[1][2] |
A[2][0] |
A[2][1] |
A[2][2] |
每个行是一个地址,每个列也是一个地址。
行地址:我用这个来控制行;列地址,我用它来控制列。
比如 我定义一个二维数组
int a[3][3];
a在这里代表的是a[0]这一行,思考一下,对a的地址+1
代表着啥物理意义?先行后列,行优先原则,肯定是下一行哇。
所以,我就有了行指针;现在想想列指针,我行指针已经有了
列指针还会远吗?一列是某行的第几个元素,就设为第j个,所以
行优先,行指针为*a;列指针是要在行的基础之上,*(*a+j)
这个样子,我就实现了列指针。
行与列指针的几种表现方式:
- a[i];(此时对a[i]自增自减的操作就是对列的操作)
例如 a[1]+2;表示第二行第二个元素,
- a; (此时对a自增自减的操作就是对行的操作,从第0行开始)
- &a[i][j];(此时对a[i][j]加减实数就是对列的操作)
- *(a+1)也变成列指针,因为a本身是行指针,加*就是行后列了。
- & a[1]+2;这样子呐?变了,首先是把行指针提出来了,行指针+2,就到了第a[3]行了。为什么?(编译器)思考。
尤其注意: 普通的指针变量,无法被赋值为行地址!!
那行地址应该被怎么表示呐?
想想指针数组咋用的?
int *a[10];这是定义,指针数组是数组,装着指针的数组
C++实现动态数组的方法
new和delete运算符实现
程序核心都在指针方面
一维动态数组实现方法
int n;
cin>>n;
int *p=new int [n]
delete如何使用?
delete [](某个指针);
怎么理解 相当于[]表示一个连续的存储空间,delete表示清除(某个指针真相的)连续存储空间。
一维数组的访问在上面
通过指针访问一维数组的元素
下面介绍二维数组的访问与动态二维数组
技术核心:指针数组
先注意一点!二维数组名并不等于二级指针;二级指针是指向指针的指针,它指向的是一级指针。
二维数组如何访问?准确来说如何通过二级指针访问二维数组
指针数组的里面存着指针,所以单个的指针数组名可以称为二级指针;代码如下:
int main ()
{
cout<<”几行几列”<<endl;
cin>>m>>n;
int **p=new int *p[m];
for (int i=0;i<m;++i)
{
*p[i]=new int [n];
}
(对二维动态数组进行操作,和普普通通的二维数组一样就行)
for (int i=0;i<m;++i)
delete []p[i];
delete []p;
p=0;
return 0;
}
怎么理解?我们知道二维数组也是线性存储的,并且按照先行后列的原则进行,先定义的是一个指针数组,里面存着每一行的指针,后定义的是列,只不过是动态申请,delete也是一样,先清除行里面的数据(也就是二维数组里面的列),后清除行里面的数据,也就是二级指针指向的地址里面存储的行信息。
下面通过对输入国家从而对国家排序(依据ASC码)代码深度理解指针
#include <iostream>
#include <cstring>
using namespace std;
void px(char *a[],int n);
void px(char *a[],int n)
{
for (int i=0;i<n;++i)
{
int xb=i;
for (int j=i+1;j<n;++j)
{
if (strcmp(*(a+j),*(a+xb))==1)
{
xb=j;
}
}
if (xb==i)
continue;
else
{char jh[20];
strcpy(jh,*(a+xb));
strcpy(*(a+xb),*(a+i));
strcpy(*(a+i),jh);}
}
}
int main()
{
cout<<"请输入一共几个国家"<<endl;
int n;
cin>>n;
char **p=new char *[n];
for (int i=0;i<n;++i)
{*(p+i)=new char [20];}
for (int i=0;i<n;++i)
{
cin>>p[i];
}
px(p,n);
for (int i=0;i<n;++i)
{
cout<<*(p+i)<<" ";
}
for (int i=0;i<n;++i)
{delete []p[i];}
delete []p;
return 0;
}