指针(进阶)

目录

指针进阶:

指针练习题: 

习题一: 

习题二: 

习题三: 

习题四: 

习题五: 

习题六: 

习题七: 

习题八: 

习题九: 

 指针sizeof和strlen习题:

习题一:

习题二: 

习题三: 

习题四: 

习题五: 

习题六: 

​编辑 习题七:

习题八:

习题八: 

 习题九:

指针进阶:

       通过指针基础我们已经了解了指针,这篇文章我们会举大量的例子,使我们对指针透彻理解,我们下来看一段代码:

int main()
{
	char a[] = "ab";
	char* pc = a;
	printf("%c\n", *pc);
	printf("%s\n", pc);
	//当指针接收的是字符串时,打印这个指针所指向的字符串不需要解引用
	return 0;
}

       当指针接收的是字符串时,打印这个指针所指向的字符串不需要解引用;可是当要打印首个元素时,需要解引用。

int main()
{
	char* ch = "abcdef";
	//"abcdef"是一个常量字符串
	printf("%c\n", *ch);
	printf("%s\n", ch);
	return 0;
}

       在这个例子中,字符串“abcdef”,储存在连续的无名储存区中,通过语句ch=“abcdef”,将无名储存区的首地址赋给指针ch,也就是说:指针变量ch指向无名储存区域的首地址,而不是把无名储存区域的内容保存在ch中,因为它们各自所占空间大小不同(指针变量大小是固定的)。

       道理同上,打印字符串不需要解引用,打印首元素需要解引用。 

       此时我们想修改其中一个字符。

int main()
{
	char* ch = "abcdef";
	//"abcdef"是一个常量字符串
	//所以*p就是首个元素
	*ch= 'W';
	printf("%s\n", ch);
	return 0;
}

       无法输出结果,因为"abcdef"是常量字符串,常量意味着不可改变,这就是错误的,所以最好这样写const char*ch,在星号前加上const,下面就直接报错了。

       我们也可以这样访问:

指针的比较: 

       在基础篇我们其实就已经已经讲过指针的比较了(详情指针(基础篇)-CSDN博客),指针比较必须是指向同一块内存的,我们来看代码。

int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abcd";
	char* p1 = "abcd";
	char* p2 = "abcd";
	if (arr1 == arr2)
		//比较的是地址
		printf("hehe\n");
	else
		printf("haha\n");
	return 0;
}

       创建了两个字符型数组,虽然内容一样,可是两个数组各有各的内存空间,地址完全不同,所以执行else。 

       但是p1和p2确实指向同一块空间,这就很奇怪,内容相同的常量字符串只会保存一份,所以他们只想的空间的地址相同。

       还有一种指针类型是空指针类型,不是整形,不是字符型,不是数组型,而是一种空指针类型。空指针类型可以接收任何类型的地址。

int main()
{
	int a = 10;
	void* p = &a;//空指针可以接收任意类型的地址
	    //void* 类型指针不能进行解引用操作
	p++;//void* 类型的指针不能进行+-操作(不知道步长)
	printf("%p\n", p);//空指针有地址
	printf("%d\n", *p);//空指针类型不能通过解引用来找到原有数据
	return 0;
}

       此时会报错,是编译性错误。

       当const修饰变量时,我们不能直接去修改它,但是还是可以通过指针修改。 

       const分为放在*的左边和右边,控制的权限不一样。

       当constant限制的是*p,意思是不能通过p来修改指向的空间内容,就是不能解引用了;当const放在*的右边,限制的是p变量,也就是p变量不能被修改了,没办法再指向其他地址了,但*p不受限制,还是可以通过p来修改所指向的变量。 

指针练习题: 

       此时我们已经几乎会所有的指针相关概念,那么我们就拿一些小习题练练手。

习题一: 


int main()
{
    int a[5] = { 1,2,3,4,5 };
    int* ptr = (int*)(&a + 1);//下一个数组的地址,就变成了数组指针类型,之后强制转换
    printf("%d,%d\n", *(a + 1), *(ptr - 1));//-1就是数组的最后一个元素
    return 0;
}

       把&a+1的地址强制付给ptr,因为取地址a+1是下一个数组的地址,所以要强制转换类型。注意:*(a+1)==a[1]。

习题二: 

       


struct Test
{
    int Num;
    char* pcNum;//64位指针大小是8字节
    short sDate;//short是两个字节
    char cha[2];
    short sBa[4];
}*p;//此结构体大小为32
int main()
{
    p = (struct Test*)0x100000;

    printf("%p\n", p + 0x1);

    printf("%p\n", (unsigned long)p + 0x1);

    printf("%p\n", (unsigned int*)p + 0x1);
    
    return 0; 
}

习题三: 

int main()
{
    int a[4] = { 1,2,3,4 };

    int* ptr1 = (int*)(&a + 1);

    int* ptr2 = (int*)((int)a + 1);

    printf("%x,%x", ptr1[-1], *ptr2);
    return 0;
}

       一定要记得,强制转换的优先级>加减的优先级。 

习题四: 

int main()
{
    int a[3][2] = { (0,1),(2,3),(4,5) };//注意是逗号运算符

    int* p;

    p = a[0]; 
    printf("%d\n", p[0]);
    printf("%d\n", p[1]);//等价于*(p+1)
    //结果为1 3
    return 0;
}

       注意是小括号,小括号里逗号是运算符。

习题五: 


int main()
{
    int a[5][5];
    int(*p)[4];//定义了一个指针数组
    p = a;

    printf("%p,%d\n", &p[4][2] - &a[4][2],&p[4][2] - &a[4][2]);
    return 0;
}

习题六: 

int main()
{
    int a[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
    int* ptr1 = (int*)(&a + 1);
    int* ptr2 = (int*)(*(a + 1));

    printf("%d,%d", *(ptr1 - 1), *(ptr2));
    //*(a+1)是第二行数组的地址,解引用后只想第二行数组的第一个元素
    return 0;
}

       二维数组如arr[1],是第一行第一个元素的地址,arr[2]是第二行第一个元素的地址。

习题七: 


int main()
{
    char* a[] = { "work","at","alibaba" };//指针数组,数组每一个元素是char*
    //每个char*都存放每个元素首元素地址

    char** pa = a;//二级指针,类型为char*,变量为指向char*

    pa++;

    printf("%s\n", *pa);
    return 0;
}

习题八: 

int main()
{
    char* c[] = { "ENTER","NEW","POINT","FIRST" };
    char** cp[]={ c + 3,c + 2,c + 1,c };
    //因为c是首元素地址,c+3指向第四个元素
    //所以cp首元素指向c的第四个元素地址
    char*** cpp = cp;

    printf("%s\n", **++cpp);

    printf("%s\n", *-- * ++cpp + 3);
    //注意此时解引用后又--了,原来c+1变为c也被保留下来
    printf("%s\n", *cpp[-2] + 3);

    printf("%s\n", cpp[-1][-1] + 1);

    printf("%s\n", **cpp);
    return 0;
}

       看一下内存布局: 

 

       注意指针的自增自减,会改变原来指针指向。 

习题九: 


int main()
{
    unsigned long pu[] = { 6,7,8,9,10 };
    unsigned long* pulptr;
    pulptr = pu;
    *(pulptr + 3) += 3;//指针指向没有改变
    //把第4个元素的值换为12
    printf("%d,%d\n", *pulptr, *(pulptr + 3));
    return 0;
}

习题十: 

struct Info
{
	char name[10];
	int num;
	float Score;
};

void f(struct Info* p)
{
	struct Info s[2] = { {"Sum", 20044, 90}, {"Peng", 20045, 87} };
	struct Info* q = s;
	*p = *q;
}

int main()
{
	struct Info s[2] = { {"Yang", 20041, 73}, {"Gou", 20042, 580} };
	f(s);
	printf("%s %d %3.0f\n", s[0].name, s[0].num, s[0].Score);
	printf("%s %d %3.0f\n", s[1].name, s[1].num, s[1].Score);

	return 0;
}

       即使上面的开辟了两个结构体空间(关于结构体的详情请看结构体和位段-CSDN博客),但是解引用的就是针对首元素地址,所以只改变了原来数组的第一个元素,其他元素没有改变。

 指针sizeof和strlen习题:

       先看几个知识点,sizeof读取‘\0’,strlen不读取‘\0’;strlen接收的是地址。一定要记得,字符串的双引号中最后包含一个'/0',可以不写,但是有。如果用大括号是字符串,没有声明个数,也没有声明'/0',也不知道字符串在哪里结束;若声明了个数,一定要有'/0'的位置。

       两个例外情况:

  1. sizeof(数组名) - 数组名表示整个数组大小。
  2. &数组名 - 数组名表示整个数组。

习题一:

int main()
{
    //数组名是元素地址
    //例外:
    //1.sizeof(数组名) - 数组名表示整个数组
    //2.&数组名 - 数组名表示整个数组
    int a[] = { 1,2,3,4 };//4*4=16
    printf("%d\n", sizeof(a));  //16    sizeof(数组名)-计算数组总大小
    printf("%d\n", sizeof(a+0));//8/4 数组名+0,这里表示首元素地址,地址大小就是指针大小
    printf("%d\n", sizeof(*a)); //4     数组名解引用,找到数据类型
    printf("%d\n", sizeof(a+1));//8/4   指针指向数组名第二个元素,是指针大小
    printf("%d\n", sizeof(a[1]));//4    数组第二个元素大小
    printf("%d\n", sizeof(&a)); //8/4    &a是数组地址,大小为指针大小
    printf("%d\n", sizeof(*&a));//16     &a使整个数组的地址,解引用后就是整个数组大小
    printf("%d\n", sizeof(&a + 1));//8/4 先&a整个数组大小,之后指向下一个数组,还是地址
    printf("%d\n", sizeof(&a[0]));//8/4  []方块优先级高,还是地址
    printf("%d\n", sizeof(&a[0]+1));//8/4 第二个元素的地址,还是地址
    return 0;
}

 

习题二: 

int main()
{
    char a[] = { 'a','b','c','d','e','f' };
    //strlen函数也是从首元素地址读取到'\0'为止
    printf("%d\n", strlen(a));     //随机值,strlen找到'\0'才停止
    printf("%d\n", strlen(a+0));   //随机值,和上一个一样
    //printf("%d\n", strlen(*a));    //随机值,'a' - 97 strlen从地址97开始寻找 报错
    //printf("%d\n", strlen(a[1]));  //和上一个一样
    printf("%d\n", strlen(&a));    //和第一个一样
    printf("%d\n", strlen(&a+1));  //和第一个相差6
    printf("%d\n", strlen(&a[0]+1));//和第一个相差1
    return 0;
}

       strlen函数读取到‘\0’停止,在大括号中没有‘\0’,所以第1个是随机数。 

习题三: 

       用指针定义一个字符串并研究大小。


int main()
{
    char* p = "abcdef";
    printf("%d\n", sizeof(p));     //8/4 p是指针,存放a的地址
    printf("%d\n", sizeof(p+1));   //8/4 b的地址,还是地址
    printf("%d\n", sizeof(*p));    //1   是数据类型的大小
    //解引用后找到a,a是字符型大小为1
    printf("%d\n", sizeof(p[0]));  //1   是数据类型大小
    printf("%d\n", sizeof(&p));    //8/4 还是地址
    printf("%d\n", sizeof(&p+1));  //8/4
    printf("%d\n", sizeof(&p[0]+1));//8/4
    return 0;
}

习题四: 

int main()
{
    char a[] = { 'a','b','c','d','e','f' };
    printf("%d\n", sizeof(a));   //6   这里不是字符串,所以是6个字节
    printf("%d\n", sizeof(a+0)); //4/8 还是地址
    printf("%d\n", sizeof(*a));  //1   解引用后就是数据类型大小 
    printf("%d\n", sizeof(a[1]));//1   数组第一个元素大小
    printf("%d\n", sizeof(&a));  //4/8 地址
    printf("%d\n", sizeof(&a+1));//4/8 地址
    printf("%d\n", sizeof(&a[0]+1));// 地址
    return 0;
}

习题五: 

int main()
{
    char a[] = "abcdef";
    printf("%d\n", sizeof(a));      //7 因为有默认'\0',sizeof读取它
    printf("%d\n", sizeof(a+0));    //8 地址
    printf("%d\n", sizeof(*a));     //1 数据类型大小
    printf("%d\n", sizeof(a[1]));   //1
    printf("%d\n", sizeof(&a));     //8 地址
    printf("%d\n", sizeof(&a+1));   //8
    printf("%d\n", sizeof(&a[0]+1));//8
    return 0;
}

习题六: 

int main()
{
    char a[] = "abcdef";
    printf("%d\n", strlen(a));      //6 strlen不读取'\0'
    printf("%d\n", strlen(a+0));    //6 首元素地址
    //printf("%d\n", strlen(*a));   //报错 非法访问
    //printf("%d\n", strlen(a[1])); //报错 非法访问
    printf("%d\n", strlen(&a));     //6 有警告,这相当于数组,应该用数组指针接收
    printf("%d\n", strlen(&a+1));   //随机值
    printf("%d\n", strlen(&a[0]+1));//5 从b开始读取
    return 0;
}

 习题七:


int main()
{
    char* p = "abcdef";
    printf("%d\n", sizeof(p));     //8/4 p是指针,存放a的地址
    printf("%d\n", sizeof(p+1));   //8/4 b的地址,还是地址
    printf("%d\n", sizeof(*p));    //1   是数据类型的大小
    //解引用后找到a,a是字符型大小为1
    printf("%d\n", sizeof(p[0]));  //1   是数据类型大小
    printf("%d\n", sizeof(&p));    //8/4 还是地址
    printf("%d\n", sizeof(&p+1));  //8/4
    printf("%d\n", sizeof(&p[0]+1));//8/4
    return 0;
}

习题八:

int main()
{
    char* p = "abcdef";
    printf("%d\n", strlen(p));        //6 a的地址被接收,之后计算长度 
    printf("%d\n", strlen(p + 1));    //5 b的地址,长度少了1
    //printf("%d\n", strlen(*p));     //报错 
    // a的ASCII码是97,从地址97处读取,报错  
    //printf("%d\n", strlen(p[0]));   //报错
    printf("%d\n", strlen(&p));       //随机值
    printf("%d\n", strlen(&p+ 1));    //随机值
    printf("%d\n", strlen(&p[0] + 1));//5 第二个元素读取地址
    return 0;
}

习题八: 

int main()
{
    int a[3][4] = { 0 };
    printf("%d\n", sizeof(a));   
    //48 总数组大小
    printf("%d\n", sizeof(a[0][0]));
    //4 第一个数组第一个元素的大小
    printf("%d\n", sizeof(a[0]));
    //16 第一个元素是一维数组,一维数组的大小
    printf("%d\n", sizeof(a[0]+1));
    //8/4 a[0]是第一行的数组名,数组名此时是首元素地址,a[0]是第一行第一个元素地址
    //所以 a[0]+1就是第一行第二个元素地址,地址的大小
    printf("%d\n", sizeof(*(a[0]+1)));
    //4 第一个数组中第二个元素的地址,解引用后找到元素,是类型的大小
    printf("%d\n", sizeof(a+1));
    //8/4 第二个元素是一维数组的地址,地址的大小
    printf("%d\n", sizeof(*(a+1)));
    //16 sizeof(a[1])第二个数组中的第一个元素的地址,解引用后是它第二个元素的大小
    printf("%d\n", sizeof(&a[0]+1));
    //8/4 第二个一维数组的地址,地址的大小
    printf("%d\n", sizeof(*(&a[0] + 1)));
    //16 第二个一维数组地址解引用,第二个一维数组元素的大小
    printf("%d\n", sizeof(*a));
    //16 第一行一维数组元素的大小
    printf("%d\n", sizeof(a[3]));
    //16 sizeof不去真实计算,可能会举一反三,根据类型计算大小
    return 0;
}

       a是二维数组的数组名,没有sizeof(数组名),也没有&(数组名),所以a是首元素地址,二维数组的首元素是第一行的一维数组。对二维数组解引用,求大小是第一行的大小。 

 习题九:

       这里可以看出指向常量字符串是指向字符串的首元素地址。 

  • 15
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值