计算机的内存最小单位是BYTE,每一个BYTE的内存都有一个唯一的编号,这个编号就是内存地址,编号在32位系统下是32位的整数,在64位系统下是64位的整数。
1、指针的含义与定义
指针变量指向的是一个地址,而不是一个整数(浮点)值
例如:int a= 0;int b = 10;char buf[10];
printf(“%p,%p,%p\n”,&a,&b,buf);//输出的是a,b的地址和buf的首地址。
int *p = &a;//int *p;p = &a;//得到变量a的地址,将这个地址赋值给变量p
printf(“%X\n”,p);//地址虽是一个整数,但地址是一个特殊的整数,是不能直接通过整数来操作的
int* p1;//定义一个变量,名字叫p1,它可以指向一个int的地址
p1 = &b;//指针变量的值一般不能直接赋值一个整数,而是通过取变量地址的方式赋值。
//*p的意思是指针变量指向内存的内容(就是整数值)
int c = *p;
printf(“%d,”,c);//0
*p = 10;
printf(“a = %d \n”,a);//10该变了a之前的值
printf(“%u,%u,%u,%u”,buf,&buf[1],&buf[2],&buf[[3]);//取数组的前4个地址
char c = 0;
char* p2 = &c;//p2指向的是一个地址该地址为c的地址(&c)
printf(“%d\n”,sizeof(p));//4 指针sizeof都为4
printf(“%d\n”,sizeof(p2));//4 指针sizeof都为4
void* p3;//这个叫无类型指针,意思是这只是一个指针变量,而不是指向任何具体的数据类型
p3 = NULL;//将指针赋值NULL,值为NULL的指针我们俗称空指针
NULL在C语言中的定义为(void*)0 空指针与野指针的区别,指向NULL的指针叫空指针,没有具体指向任何变量地址叫野指针。
int *p;
*p = 1;
printf(“%d\n”,*p);//p就是一个野指针,程序中要避免野指针的存在,因为野指针是导致程序崩溃的主要原因,空指针是合法的,但野指针是违法的。
指针与数组的关系
l 指针的兼容性
不能从这种类型转换成其他类型,原则上一定是相同类型的指针指向相同类型的变量地址,不能用一种类型的指针指向另一种类型的变量地址
比如:char b = 4;
int * p = &b;
printf(“%d\n”,*p);//为随机值,不是b的值,下面会弹出提示,说类型不一致
float a = 3.14;
int i = a;//自动数据类型转化,将浮点数的小数部分舍弃
int * p = &a;
printf(“%d\n”,*p);//严重的错误,因为指针类型不一致
printf(“%d\n”,i);//3
char buf[10] = {0,1,2,3,4};
char* p = buf;//等价与&buf[0]
char* p1 = &buf[1];
char* p2 = &buf[2];
char* p3 = &buf[3];
printf(“%d,%d,%d,%d”,p,p1,p2,p3);//打印出各个指针的地址
*p2= 7;//数组第3个元素值为7
p3 += 1;
*p3 = 100;//数组的第5个元素值100(下标为4的元素)
for(int i = 0;i <10;i++){//循环改变每个值
*p = i;
p++;
}
//如果中间不加什么,这两个for循环会导致崩溃
//p已经指向的数组的最后一个成员啦
p = buf ;//将p的值重新指向了数组的首元素 p -= 10;也可以
for(int i = 0;i <10;i++){//循环改变每个值
*p = i * 10;
p++;
}
int a = 0x12345678;
char*p = &a;//这违反了指针的兼容性,容易造成数据的丢失
p++;
printf(“%x\n”,a);//56,如果没有p++,就是78,如果再加一个p++,就是34 如果指针不兼容的话,*p只能看到其中的一部分,不能看到所有的值
指向常量的指针与常量指针
const char*p;//定义一个指向常量的指针
char* const p;//定义一个指针常量,一旦初始化之后其内容不可改变
例如:
int a = 10;
//指向常量的指针
const int *p = &a;//p这个指针只能指向一个变量
//*p = 20;//不能通过*p的方法修改一个const指针
a = 20;
printf(“%d\n”,*p);//20
//常量指针
int b = 30;
int* const p =&a;//定义一个常量指针,可以通过常量指针修改或者读取一个变量的值
*p = 20;
//p = &b;//错误,常量指针一旦定义了,那么就不能修改其指向的变量 就是定义了a,就不能定义了b
printf(“%d”,a);//20
指针操作实现计算字符串长度以及合并字符串
char s1[100] = ”hello”;
char s2[100] = “world”;
//用指针来求一个字符串的长度,不可以使用数组下标的方式
//用指针来将s1和s2合并为一个字符串,结果放入s1中,不可以使用数组下标的方式
char* p1 = s1;
int len = 0;
while(*p1){//*p1的内容为0的时候,表示字符串结束了
p1++;
len++;
}
printf(“%d\n”,len);//5
//s1和s2总共的字符串长度
char* p2 = s2;
while(*p2){
*p1 = *p2;//从s1的最后开始,从s2的首元素开始
p2++;
p1++;
//上面三句可以用*p1++ = *p2++;
}
printf(“s1 = %s\n”,s1);//helloworld
指针的运算
指针运算不是简单的整数加减法,而是指针指向的数据类型在内存中占用字节数作为倍数的运算。
char* p;
p++;//移动了sizeof(char)这么多的字节数
int*p1;
p1++;//移动了sizeof(int)这么多字节数
int buf[10];
int *p1 = &buf;
int *p2 = &buf[1];
int*pp1 = &buf[1];
int*pp2 = &buf[3];
printf(“%d\n”,pp2 - pp1);//2 =4(int)/4(int)*2(下标距离)两个指针相减可以得到两个数组元素的相对距离
printf(“%d\n”,(int)pp2 - (int)pp1);//8
char*pp1 = &buf[1];
char*pp2 = &buf[3];
printf(“%d\n”,pp2 - pp1);//8 =4(int)/1(char)*2(下标距离)两个指针相减可以得到两个数组元素的相对距离
long long*pp1 = &buf[1];
lonog long *pp2 = &buf[3];
printf(“%d\n”,pp2 - pp1);//1=4(int)/8(long long)*2(下标距离)两个指针相减可以得到两个数组元素的相对距离
short *pp1 = &buf[1];
short *pp2 = &buf[3];
printf(“%d\n”,pp2 - pp1);//4=4(int)/2(short)*2(下标距离)两个指针相减可以得到两个数组元素的相对距离
在地址中,int类型数组相邻的地址相差一个int(4)字节,short(2)个字节,char(1)个字节,long long(8)个字节
3、指针实现数组逆置以及求数组最大元素
//使用指针实现冒泡排序
int buf[10] = {12,45,13,45,143,19,15,30,20,13};
for(int i = 0;i < 10;i++){
for(int j= 1;j < 10-j;j++){
if(*(s+j) < *(s+j - 1)){
int temp = *(s + j);
*(s+j) = *(s+j - 1);
*(s + j -1) = temp;
}
}
}
for(int k = 0;k < 10;k++){
printf(“%d\n”,*(s + i));//从小大排序
}
//使用数组来求出数组中的最大值
int value = buf[0];
for(int i = 1;i < 10;i++ ){
if(value < s[i]){
value = s[i];
}
}
printf(“%d\n”,value);//143
//使用指针来求出数组中的最大值
int value = *s
for(int i = 1;i < 10;i++ ){
if(value < *(s+i)){
value =*(s+i);
}
}
printf(“%d\n”,value);//143
//数组顺序倒转(数组实现)
int low = 0;
int high= 9;
while(low < high){
int temp = buf[low];
buf[low] = buf[high];
buf[high] = temp;
low++;
high--;
}
for(int k = 0;k < 10;k++){
12,45,13,45,143,19,15,30,20,13
printf(“%d\n”,buf[i]);//13,20,30,15,19,143,45,13,45,12
}
//数组顺序倒转(指针实现)
int *start = &buf[0];
int *end =&buf[9];//或者int* end = start + 9;
while(start > end){
int temp = * start;
*start = *end;
*end = temp;
start++;
end--;
}
for(int k = 0;k < 10;k++){
12,45,13,45,143,19,15,30,20,13
printf(“%d\n”,*(s+i));//13,20,30,15,19,143,45,13,45,12
}
指针实现计算数组第二大元素
第一步定义两个变量,分别为最大max和第二大smax,第二步先默认数组中第一个和第二个元素分别为最大和第二大。当遇到大于max的元素smax = max,max= 大于max的元素,如果遇到小于max,但大于smax的元素,那么就让smax等于该元素。
int buf[10] = {12,16,17,18,13,15,14,20,11,22};
//先用数组方式实现
//先比较第一个和第二个
int max = 0;
int s_max = 0;
if(s[0] > s[i]){
max = s[0];
s_max = s[1];
}else{
max = s[1];
s_max = s[0];
}//将max等于s[0]和s[1]中大的那个元素的值
int i;
for(i =2;i < 10;i++){//从第3个元素开始遍历数组
if(max < s[i]){//如果遇到大于max的元素,那么让s_max等于max,让max等于这个元素
s_max = max;
max = s[i];
}else if(max > s[i] && s[i] > s_max){//如果这个元素是介于max和s_max之间,那么就让这个元素等于s_max
s_max = s[i];
}
}
printf(“%d”,s_max);//20
//用指针方式实现
//先比较第一个和第二个
int max = 0;
int s_max = 0;
if(*s > *(s + 1)){
max = *s
s_max = *(s + 1);
}else{
max = *(s + 1);
s_max = *s;
}//将max等于s[0]和s[1]中大的那个元素的值
int i;
for(i =2;i < 10;i++){//从第3个元素开始遍历数组
if(max < *(s + i)){//如果遇到大于max的元素,那么让s_max等于max,让max等于这个元素
s_max = max;
max = *(s + i);
}else if(max > *(s + i) && *(s + i) > s_max){//如果这个元素是介于max和s_max之间,那么就让这个元素等于s_max
s_max = *(s + i);
}
}
printf(“%d”,s_max);//20
指针实现汉字字符串逆置
实现字母逆置
char str[100] = “you”;
char* str_start = &str[0];
char+ str_end = &str[strlen(str ) - 1];
while (str_start < str_end){
char* temp = *str_start;
*str_start = *str_end;
*str_end = temp;
str_start++;
str_end--;
}
printf(“%s\n”,str);
实现汉字逆置
//对于VS的汉字是GBK的编码,一个汉字2个字节,对于QT汉字是UTF8编码,一个汉字是3个字节
char str[100] = “你好我也好”;
short* str_start = &str[0];
short* str_end = &str[strlen(str ) - 2];
while (str_start < str_end){
short* temp = *str_start;
*str_start = *str_end;
*str_end = temp;
str_start++;
str_end--;
}
printf(“%s\n”,str);
指针的复习1
1、屏蔽错误
在头文件加入#pragma warning(disable::错误编码)//错误编码如4470
2、野指针不能用来判断真假,否则会报错
3、计算数组和指针的sizeof
int buf[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = buf;
printf(“%d,%d\n”,sizeof(p),sizeof(buf));//4(指针类型) 40=4*10(数组类型)
for(int i = 0;i< 10;i++){
printf(“%d”,buf[i]);//p[i]或者*(p+i)等价于buf[i] C语言允许指针通过数组下标的方法访问数组成员
}
指针数组以及多级指针
1、int *a[10];//定义了一个指针数组,一共10个成员,其中每个成员都是int*类型。
printf(“%d,%d\n”,sizeof(a),sizeof(a[0]));//40 4
short*b[10];//定义了一个指针数组,一共10个成员,其中每个成员都是short*类型。
printf(“%d,%d\n”,sizeof(b),sizeof(b[0]));//40 4
2、int a = 10;
int *p = &a;
int **p;
pp = &p;//定义了一个二级指针,指向了一个一级指针的地址
**pp = 100;//通过二级指针修改内存的值 通过**pp访问a,通过*pp访问p
//*pp = 10;//相当于将p指向了编号为10的这块内存,pp还是正常的指针,但p被修改成了野指针//通过*p访问a
printf(“a = %d\n”,a);//100
int ***ppp = &pp;
***ppp = a;
printf(“a = %d\n”,a);//0
指向多维数组的指针
1、int buf[2][3] = {{1,2,3},{4,5,6}};
//int* p[3];//指针数组
int(*p)[3];//定义了一个指针,指向int[3]这种数据类型,指向二位数组的指针
p =buf;//p指向了二维数组的第一行
printf(“%d\n”,sizeof(p));//4
printf(“%d,%d\n”,p,p+1);//位移了1 * sizeof(int * 3) = 12
//打印二维数组
for(int i = 0;i < 2;i++)
{
for(int j = 0;j < 3;j++){
printf(“%d\n”,buf[i][j]);//123456可以用*(*(p + i) + j)或者p[i][j]代替
}
}
二维数组:
int buf[3][5] |
二维数组名称,buf表示数组首地址 |
int(*a)[5] |
定义一个指向int[5]类型的指针变量a |
a[0],*(a + 0),*a |
0行,0列元素地址 |
a + 1 |
第一行首地址 |
a[1],*(a + 1) |
第一行,0列元素地址 |
a[1] + 2,*(a + 1) + 2,&a[1][2] |
第一行第二列元素地址 |
*(a[1] + 2),*(*(a + 1) + 2),a[1][2] |
第一行第二列元素的值 |
const保护函数参数以及返回值为指针的函数
1、函数的参数可以是指针类型。它的作用是将一个变量的地址传送给另一个函数。通过函数的指针参数可以间接的实现形参修改实参的值。
经常用指针访问数组
int main(){
int a = 10;
test(&a);//test变量n指向的是一个地址
//scanf(“%d”,&i);//所有需要在函数内部修改实参的值,都需要通过将指针作为函数参数调用实现
int b = 10;
int c = 20;
swap(&b,&c);//swap(b,c)是不可行的
printf(“%d%d”,b,c);//20 10
int buf[10] = {1,2,3,4,5,6,7,8,9,10};
printf(“buf = %d”,sizeof(buf));//
set_array(buf);//放的是数组的首地址
print_array(buf,sizeof(buf)/sizeof(int));
int *p = &a;
*p = 10;
printf(“%d”,*p);//10
//很少直接用指针指向一个变量,然后通过*方式访问变量
return 0;
}
void test(int *n)
{
(*n)++;
}
//交换
void swap(int* a,int * b){
int temp = *a;
*a = *b;
*b = temp;
}
void set_array(int buf[])//数组名代表数组的首地址 int* buf等价于int buf[]当数组名作为函数的参数时,数组名其实就是一个指针变量一维数组名作为函数参数时,C语言将数组名解释为指针
{
printf(“buf = %d”,sizeof(buf));
buf[0] = 100;
buf[1] = 200;
//*buf = 100;
//buf++;
//*buf = 200;
}
void print_array(int * buf,int n){
int i ;
for(i = 0;i < n;i++){
printf(“buf[%d] = %d”,i,buf[i]);
}
}
返回数组指针
因为数组不能被拷贝,所以函数不能返回数组。不过,函数可以返回数组的指针或者引用。要想定义一个返回数组的指针或引用比较繁琐,所以最直接的方法是使用类型别名。
tydedef int arrT[10];//arrT是一个类型别名,它表示的类型是含有10个整数的数组
using arrT = int[10];//arrT的等价声明
arrT* func(int i);//func返回一个指向含有10个整数的数组的指针。