二级指针
要清楚二级指针,首先我们介绍一级指针
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;
}