初阶指针(2)+初识结构体 第18课

二级指针

要清楚二级指针,首先我们介绍一级指针

int main()
{
	int a = 10;
	int *pa = &a;
	*pa = 20;
	printf("%d\n", a);
	return 0;
}

pa中存放的是a的地址,存放地址的变量叫做指针变量,因为pa通过一次解引用就能访问到a的值,所以我们把pa叫做一级指针变量,也叫做一级指针。

我们看打印结果

我们可以发现,通过一次解引用就改变了a的值。

有了一级指针,那么二级指针就更加简单了

int main()
{
	int a = 10;
	int*pa = &a;
	int**ppa = &pa;
	**ppa = 20;
	printf("%d\n", a);
	return 0;
}

在途中,ppa就是二级指针,由代码可知:二级指针中存放的是一级指针变量的地址 通俗的讲:

假设a的地址是0x11223344 ,pa表示存放a的地址的变量,变量pa的地址是0x11334455,那么二级指针ppa就存放的是变量pa的地址。

如何识别二级指针呢?

我们首先介绍一下指针变量的判断方法

例如int a=10;int 表示a的类型是整型,int*pa=&a;这里的*表示pa是一个指针,int表示指针指向的变量是一个整型类型,int**ppa=&pa;靠近ppa的最近的*表示ppa是一个指针,剩下的int*表示ppa指针指向的变量的类型是一个指向整型的指针。

这里有一个比较容易出错的点:二级指针是不是就是地址的地址?

错误!地址不是变量,地址已经是最小的单元了,不能够再取地址了,二级指针实际上的含义是存放一级指针的变量的地址的地址,是变量的地址而不是地址的地址。

二级指针如何解引用

上图中的二级指针ppa实际上存放的是一级指针变量的地址,我们对它解引用表示访问一级指针变量,一级指针变量中存放的又是整型变量的地址,我们再进行解引用,访问其所指向的整型变量a,我们可以发现,一级指针只需要解引用一次就能找到其所对应的变量,而二级指针需要解引用两次

下面介绍一个新知识点,指针数组

指针数组是指针还是数组?

答:数组,数组内部的元素是指针,所以叫做指针数组

我们先看看普通的数组

int a = 10;
	int b = 20;
	int c = 30;
	int arr[10] = { a, b, c };

假设我们要存放的变量的类型是指针呢

int*pa = &a;
	int *pb = &b;
	int*pc = &c;

我们该如何创建数组存储指针

int*pa = &a;
	int *pb = &b;
	int*pc = &c;
	int*arr[10] = { pa, pb, pc };

int*arr[10] = { pa, pb, pc };这串代码如何解释

因为[]的结合性比较强,所以int*arr[10],首先表示arr是一个数组,数组内部的元素的类型是int*(指向整型的指针变量)

如何通过指针数组访问到内部指针所指向的元素

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int arr[10] = { a, b, c };

	int*pa = &a;
	int *pb = &b;
	int*pc = &c;
	int*parr[10] = { pa, pb, pc };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(parr[i]));
	}
	return 0;

这串代码有几个点需要讲一下

int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%d ", *(parr[i]));
	}

前面的表示创建整型数组和指针数组,这串代码是用来访问指针对应的元素的

因为我们在指针数组中存放了3个元素到数组parr中,所以数组parr的前三个元素为pa,pb,pc,也就是&a,&b,&c,parr[i]就表示数组的第i个元素,因为数组内部的元素是指针,我们再对parr[i]进行解引用就能得到指针所对应的元素,打印结果

 那么,指针数组具体有什么用呢?

下面,我们介绍一种用途,用指针数组来打印出二维数组

int main()
{
	int arr[3][4] = { 1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
return 0;
}

上面这种方法是最简单的二维数组的打印,接下来,我们用指针数组来实现他 

int main()
{
	int arr1[4] = { 1, 2, 3, 4 };
	int arr2[4] = { 2, 3, 4, 5 };
	int arr3[4] = { 3, 4, 5, 6 };
	int *parr[3] = { arr1, arr2, arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d ", parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

 要注意,我们的数组指针parr中存放的arr1,arr2等都是数组首元素的地址

如何解释 parr[i][j]是数组元素?

在数组中,[]其实就表示解引用,例如parr[2]就相当于*parr(i+2)=arr3

arr3[i]则表示*(arr3+i)表示数组arr3的第i个元素

所以parr[i][j]=arri[j]=数组元素

结构体介绍

结构体是一些值的集合,这些值叫做成员变量,结构的每个成员可以是不同的变量

结构体的形式是这样的

struct tag
{
	member - list;
}variable - list;

 struct tag是结构题标签的意思,tag可以更改,struct不可以更改

membei-list表示成员列表,列表内的类型也叫成员变量,成员变量内部可以是不同的变量

variable - list表示结构体变量

结构体是一种集合,数组也是一种集合

不同的点是数组内部的变量类型都是相同的,结构体内部的变量类型可以不同

这些类型叫做内置类型,例如int short char long float double

复杂对象的描述就用结构体

结构体类型的声明

struct peo
{
	char name[20];
	char tele[12];
	char sex[5];
	int high;
}a1,a2;

我们创建一个结构体,struct peo为结构体标签,我们这里的标签是‘人’的意思,我们的成员变量依次是name tele sex high 分别表示姓名,电话,性别,身高

这里的a1,a2是可有可无的,这里的a1和a2是我们创建的结构体变量,这里创建的相当于全局变量,我们之前讲过要尽量避免使用全局变量,那我们该如何创建结构体变量呢?

struct peo
{
	char name[20];
	char tele[12];
	char sex[5];
	int high;
};
int main()
{
	struct peo p1;
	return 0;
}

只要我们前面声明了结构体的类型,我们就可以在main函数内部创建结构体变量,优点是只会在main函数中发挥作用

什么是结构体类型

这里的struct peo表示结构体类型,这里的p1表示结构体变量

注意:结构体变量就相当于房子的图纸,结构体变量相当于实际建成的房子,所以我们构建结构体类型是不占用内存空间的,我们创建的结构体变量才占用内存

结构体成员的类型

结构体成员可以是整型,数组,指针,甚至也可以是其他结构体

例如

struct peo
{
	char name[20];
	char tele[12];
	char sex[5];
	int high;
};
struct St
{
	struct peo p;
	int num; 
	float f;
};

一个结构体的成员变量如果有结构体变量,我们在前面必须要对这个结构体的类型定义

结构体变量的初始化

我们在创建结构体变量的时候,可以对他进行初始化,例如

struct peo
{
	char name[20];
	char tele[12];
	char sex[5];
	int high;
};
struct St
{
	struct peo p1 = { "张三", "15596668862", "男", 181 };
	return 0;
};

这是普通的结构体变量的初始化,如果是结构体变量嵌套的初始化呢?

例如:

struct peo
{
	char name[20];
	char tele[12];
	char sex[5];
	int high;
};
struct st
{
	struct peo p;
	int num;
	float f;
};
int main()
{
	struct peo p1 = { "张三", "15596668862", "男", 181 };
	struct st s = { { "lisi", "15596668888", "女",166 },100,3.14f };
	return 0;
};

结构体变量的嵌套需要两个括号来表示

在初始化的时候,也可以不完全初始化,例如

struct peo
{
	char name[20];
	char tele[12];
	char sex[5];
	int high;
};
int main()
{
	struct peo p1 = { "张三" };
	
	return 0;
};

例如,我们只初始化一个张三

可以发现,代码依然能够正常运行。

如何打印结构体

struct peo
{
	char name[20];
	char tele[12];
	char sex[5];
	int high;
};

int main()
{
	struct peo p1 = { "张三" "15596668862", "男", 181 };
	printf("%s %s %s %d\n", p1.name, p1.tele, p1.sex, p1.high);
	return 0;
};

 结构体变量名+.加成员变量名表示结构体变量初始化的结果

运行结果如图所示 

如何打印结构体嵌套的问题呢?

struct peo
{
	char name[20];
	char tele[12];
	char sex[5];
	int high;
};
struct st
{
	struct peo p;
	int num;
	float f;
};
int main()
{
	struct peo p = { "张三", "15596668862", "男", 181 };
	struct st s = { { "lisi", "15596668888", "女", 166 }, 100, 3.14f };
	printf("%s %s %s %d %d %f\n", s.p.name, s.p.tele, s.p.sex, s.p.high, s.num, s.f);
	return 0;
};

打印结果如图所示 

 我们如何使用结构体指针呢

struct peo
{
	char name[20];
	char tele[12];
	char sex[5];
	int high;
};
void print(struct peo *sp)
{
	printf("%s %s %s %d\n", sp->name, sp->tele, sp->sex, sp->high);
}

int main()
{
	struct peo p = { "张三", "15596668862", "男", 181 };
	print(&p);
	return 0;
}

注意:传递结构体地址时,要使用相同结构体类型的指针来接受,例如struct peo *sp

使用指针来打印结构体初始化的内容时,需要用指针名->来表示

结果如图

那我们能不能传递结构体变量呢?

答案是可以

struct peo
{
	char name[20];
	char tele[12];
	char sex[5];
	int high;
};
void print(struct peo p)
{
	printf("%s %s %s %d\n", p.name ,p.tele ,p.sex,p.high);
}

int main()
{
	struct peo p = { "张三", "15596668862", "男", 181 };
	print(p);
	return 0;
}

 当传递的是结构体变量时,我们就需要创建一个结构体变量来接收,例如struct peo p

传递的不是地址时,我们访问成员变量的内容也变成p.成员变量名

这种方法不如传递指针的方法,原因是我们传递指针时,只需要临时拷贝一份指针,然后调用指针访问结构体就可以了,只消耗了四个字节 当我们传递结构体变量时,我们需要拷贝一份结构体变量,结构体变量是比较庞大的,会浪费内存

习题课

如何求二进位制中1的个数

int count_num_of(unsigned int a)
{
	int count = 0;
	while (a)
	{

		if (a % 2 == 1)
		{
			count++;

		}
		a /= 2;
	}
	return count;
}
int main()
{
	int a = 0;
	printf("输入要检测的数字");
	scanf("%d", &a);
	int ret = count_num_of( a);
	printf("%d", ret);
	return 0;
}

 最简单的方法,我们说一下函数的逻辑

首先,我们假设我们要打印十进位制中1的个数,假设十进位制数字为1234

我们肯定要把个位,十位,百位,千位都求出来,然后判断他们是不是1

个位:1234%10=4,个位=4

接下来我们用1234/10=123

十位:123%10=3 十位=3

123/10=12

百位:12%10=2

12/10=1

千位:1%10=1

1/10=0

循环终止

我们如果要求二进位制,直接将10换成2即可

int count = 0;
	while (a)
	{

		if (a % 2 == 1)
		{
			count++;

		}
		a /= 2;
	}

我们输入一个数字,例如11,11的二进位制为1011

11%2=1 表示最后一位的1 ,count++,11/2=5

5%2=1 表示倒数第二位的1,count++,5/2=2

2%2=0 表示倒数第三位的0 count不加 2/2=1

1%2=1 表示第一位的1 count++,1/2=0

循环结束

所以1的个数为3

 但我们如果输入一个负数呢

 可以发现,结果不正确,我们看一下为什么不正确

我们输入-1,进入while循环,a%2的结果为-1,count不加,-1/=2的结果为0,所以循环终止,count=0

实际上的-1的二进位制中1的个数应该是什么呢?

 -1在内存中存的是补码,所以1的个数应该为32

如何进行改进

最简单的方法如下

int count_num_of(unsigned int a)
{
	int count = 0;
	while (a)
	{

		if (a % 2 == 1)
		{
			count++;

		}
		a /= 2;
	}
	return count;
}
int main()
{
	int a = 0;
	printf("输入要检测的数字");
	scanf("%d", &a);
	int ret = count_num_of( a);
	printf("%d", ret);
	return 0;
}

因为我们的a是无符号数,对应的补码会被系统识别为正数,并且首位的1不再被识别为符号位,就能打印出我们所要的结果

 下一种方法更好

int count_num_of(int a)
{
	int count = 0;
	int i = 0;
	for (i = 0; i < 32; i++)
	{
		if ((a >> i) & 1 == 1)
		{
			count++;
		}
	}
	return count;
}
int main()
{
	int a = 0;
	printf("输入要检测的数字");
	scanf("%d", &a);
	int ret = count_num_of(a);
	printf("%d", ret);
	return 0;
}

&为按位与,只有同为1的时候,结果为1,其他时候,结果都为0

最好的方法

int count_num_of(int a)
{
	int count = 0;
	while (a)
	{
		a = a&a - 1;
		count++;
	}
	return count;
}
int main()
{
	int a = 0;
	printf("输入要检测的数字");
	scanf("%d", &a);
	int ret = count_num_of(a);
	printf("%d", ret);
	return 0;
}

我们进行解释,假设我们输入的数字为11

11的二进位制为1011

  1011

&1010

=1010  找到了一个1

&1001

=1000   找到了一个1

&0111 

=0           找打了一个1

循环结束

牛客网

leetcode

如何判断一个数是否为2^n

int main()
{
	int a = 10;
	
	printf("请输入要检验的数字");
	scanf("%d", &a);
	if ((a&a - 1) == 0)
	{
		printf("是2^n");
	}
	else
	{
		printf("不是2^n");
	}
	return 0;
}

为什么这样写呢?

我们写几个2^n对应的二进位制数字

2=10

4=100

8=1000

16=10000

由此可见,2^n对应的二进位制数子只有首位为1,其他全为0,

a&a-1则表示去掉一个1,如果去掉一个1,这个数子变成了0,就表示这个数字是2^n


 

 

 如图所示

如何求两个数字对应的二进位制中的奇数位和偶数位

int main()
{
	int a = 0;
	scanf("%d", &a);
	int i = 0;
	for (i = 30; i >= 0; i-=2)
	{
		printf("%d ", (a >> i)&1);
	}
	printf("\n");
	for (i = 31; i >= 0; i-=2)
	{
		printf("%d ", (a >> i)&1);
	}
	return 0;
}

下一个题目

这段代码的结果是什么?

int i;
int main()
{
	i--;
	if (i > sizeof(1))
	{
		printf(">\n");
	}
	else
	{
		printf("<\n");
	}
	return 0;
}

这里我们补充几点

1:全局变量和静态变量都在静态区

2:全局变量和静态变量不初始化会被默认为0

3:局部变量不初始化会被默认为随机值

这里的i是全局变量,未初始化默认为0

所以i--的结果为-1

sizeof(1)的结果为4,正常情况下-1<4,应该执行else内部语句,但结果是

 证明执行了if语句的内容,为什么呢?

因为我们的sizeof的返回值的类型是size_t,是无符号整型,当无符号整型和普通整型作比较,我们的整型进行算数转化,转换成为-1的补码对应的无符号数字,2^32,远大于4,所以执行if内部的语句

打印一个x图形

int main()
{
	int n = 0;
	while (scanf("%d", &n) == 1)
	{
		int i = 0;
		int j = 0;
		for (i = 0; i < n; i++)
		{
			for (j = 0; j < n; j++)
			{
				if (i == j)
				{
					printf("*");
				}
				else if (i + j == n - 1)
				{
					printf("*");
				}
				else
				{
					printf(" ");
				}
			}
			printf("\n");
		}
	}
	return 0;
}

while (scanf("%d", &n) == 1)这里表示代码可以多次使用,每次使用的时候都调用scanf函数

输入年份月份,返回这个月的天数

int is_leap_year(y)
{
	return ((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0));
}
int main()
{
	int y = 0;
	int m = 0;
	int d = 0;
	int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
	while (scanf("%d%d", &y, &m) == 2)
	{
		d = days[m];
		if (is_leap_year(y) == 1 && m == 2)
		{
			d++;
		}
		printf("%d", d);
	}
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值