本人0基础开始学编程,我能学会的,你也一定可以,学会多少写多少。
下载安装请从官网入手,社区版本即可,这里主要使用的软件是VS2019,图标如下。
上一篇:从0开始学c语言-过渡-函数递归、循环语句、数组练习_阿秋的阿秋不是阿秋的博客-优快云博客
在开始之前,希望大家先看看我之前写的操作符文章,这里给上链接。(因为有重复的内容,细节可能不会在这里提及。)
从0开始学c语言-07-认识一下操作符、原码和反码和补码_阿秋的阿秋不是阿秋的博客-优快云博客
目录
操作符
1·算数操作符
+ - * / % |
从0开始学c语言-07-认识一下操作符、原码和反码和补码_阿秋的阿秋不是阿秋的博客-优快云博客
详细的看上面那篇文章,这里只给上结论。
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
2·移位操作符
<< 左移 | >> 右移 |
移位操作符的操作数只能是整数(不能<<2.3),我们这里所指的移动是移动补码的二进制位!!!
详细解释看从0开始学c语言-07-认识一下操作符、原码和反码和补码_阿秋的阿秋不是阿秋的博客-优快云博客
左移操作符
左移原则:左边不要,右边补0
右移操作符
右移分为两种:
2. 算术移位:左边用原该值的符号位填充,右边丢弃
计算机系统中的右移以算术移位为主。
对于移位运算符,不要移动负数位,这个是标准未定义的。
例如:
int aqiu = 10;
num>>-1; //这是不可以的哦!
补充
变量b储存a变量移位后的结果,a本身不会改变。
我们把移位放在判断条件中,可以看到a本身依旧不会改变。
3·位操作符
&:按位与(有0为0)
| :按位或(有1为1)
^:按位异或(相同为0,不同为1)
注意:
1.和移位运算符一样,他们的操作数也必须是整数。也就是说不能&2.3
2.和移位运算符一样,位操作符也是对补码进行操作。
3·和移位运算符一样,别的变量储存它按位后的结果或者放在判断条件中,它本身不变。
练习1
不创建第三个变量,交换两个变量的值。
第一种写法
int main()
{
int a = 3;
int b = 5;
printf("a=%d,b=%d", a, b);
a = b - a;
b = b - a;
a = a + b;
printf("a=%d,b=%d", a, b);
return 0;
}
这样虽然可以,但是如果a和b都很大的话会溢出。
第二种写法
int main()
{
int a = 3;
int b = 5;
printf("a=%d,b=%d", a, b);
//异或的结果同原来的其中一个值异或能得到另外一个值
a = b ^ a;
//这个a相当于密码
b = b ^ a;
//b去异或这个密码就能得到a,相当于b=b^b^a,b^b=0,0^a=a,b=a
a = a ^ b;
//a去异或这个密码就能得到b
printf("a=%d,b=%d", a, b);
//用ab异或的结果可以知道ab哪些位不同,然后用a与结果异或,相同保留不同取异得到b
return 0;
}
异或的结论
1· 任何两个相同的数 异或 为0
2· 0和任何数字 异或 是它本身
练习2
求一个整数储存在内存中的二进制1的个数
思路:一个数和1按位与的话,只有它也为1才能得到1。然后不断右移这个数,验证有多少个1。
画图示意
假设现在我们需要验证变量a=5在内存中二进制1的个数。
因为a是正数,所以原码补码相同,直接写出来便可进行运算,就像这样。
这样就可以验证有几个1了,只要&后的结果不为0便证明有1。
思路中说的是右移a这个数,但是右移负数的话就会补充很多1,那边不便不是我们想要的结果了。
我们需要换一种思路,比如:左移1。就像图中这样。
可以看到,这样确实可以达到我们的目的,那么接下来就写代码把
代码思路
int main()
{
int a = 5; //需要验证的数
int count = 0; //计数
if (a & 1)
{
count++;
}
printf("%d\n", count);
return 0;
}
根据上面的思路可以写出来这段代码。
但是我们需要验证32位二进制数字才算完成了任务,现在只是验证了一位。
那么就需要循环来帮助我们了,第一次不需要移位,那么初始值就为0。
第二次b需要移位1,第三次b需要移位2,一共32次,以此类推,就可以写出这段代码。
int main()
{
int a = 5; //需要验证的数
int b = 0; //需要左移的数
int count = 0; //计数
for (b = 0; b < 32; b++)
{
if (a & (1 << b))
count++;
}
printf("%d\n", count);
return 0;
}
优化
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
while (num)
{
count++;
num = num & (num - 1);
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
这种想法很难想到,我就不解释了。
4·赋值操作符
=与==不同
+= -= *= /= %=
>>= <<= &= |= ^=
不多讲,看从0开始学c语言-07-认识一下操作符、原码和反码和补码_阿秋的阿秋不是阿秋的博客-优快云博客
5·单目操作符
! 逻辑反操作
-
规定0为假,非0为真
-
!逻辑取反就是真假互换,一般0会变为1,非0变为0。
int main()
{
int flag = 0; //flag为假
if (flag)
{
printf("1"); //不能打印,因为flag为假
}
//flag为假的时候进行打印
if (!flag) //!falg把假的flag变为真,执行语句
{
printf("2");
}
return 0;
}
sizeof 操作数的类型长度(以字节为单位)
1.siizeof是一个操作符,而不是函数(函数不能省略括号)
2.sizeof可以计算类型或者变量所占空间的大小,sizeof的单位是字节
括号与数组类型
int main()
{
int a = 0;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);
printf("%d\n", sizeof int); //这个不行
//不能省略类型的括号
//sizeof可以省略变量的括号,说明它只是一个操作符而不是函数
//因为函数的括号不能省略
int arr[10] = { 10 };
//整个数组大小
printf("%d\n", sizeof(arr));
//int [10]就是数组的类型,代表整个数组
printf("%d\n", sizeof(int[10]));
return 0;
}
数组传参与sizeof
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4)
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));//(1)
printf("%d\n", sizeof(ch));//(3)
test1(arr);
test2(ch);
return 0;
}
可以看到,在数组传参的时候,传过去的是首元素的地址。
sizeof括号中的表达式不参与运算
int main()
{
short i = 5;
int j = 5;
printf("%d\n", sizeof(i = j + 2));//sizeof括号中的表达式不参与运算
printf("%d\n", i);
return 0;
}
sizeof算的是i的类型大小,i的值还是5。表达式的类型是看左值的类型。
为了加深理解,我们用这段代码来试试。
int main()
{
short i = 5;
int j = 6;
long long p = 9;
printf("%d\n", sizeof(i = j + 2));//sizeof括号中的表达式不参与运算
printf("%d\n", i);
printf("%d\n", sizeof(i+j)); //整型提升
printf("%d\n", sizeof(p+i)); //整型提升
printf("%d\n", sizeof(i=i+j));
printf("%d\n", sizeof(j=i+j)); //表达式的类型是看左值的类型
printf("%d\n", sizeof(p=i+j));
return 0;
}
这里涉及到 整型提升 新的知识点,以后再讲。
~ 对一个数的二进制按位取反
和它相似的
int main()
{
int a = 7;
int b = ~a;
int c = a & 1;
int d = a << 1;
printf("%d\n", a);
//a不会变
}
移位操作符(<<、>>)和位操作符(&、|、^)和单目操作符~都是
1.操作数是整数。
2.对补码进行操作。
3·别的变量储存它按位后的结果或者放在判断条件中,它本身不变。
应用练习
把a=13的二进制补码1101前面的0变为1后,再变为0。
int main()
{
int a = 13;
//能把1101前面的0变为1
a = a | (1 << 4);
//00001101 a
//00010000 1 << 4
//00011101
//这样就能把1101前面的0变为1
//把1101前面的1变为0
//第一种
a = a ^ (1 << 4);
//00011101 a
//00010000 1 << 4
//00001101
//第二种
a = a & ~(1 << 4);
//00010000 1 << 4
//11101111 ~(1 << 4)
//00011101 a
//00001101
//&=~是置0 ,|= 是置1
return 0;
}
以上这种代码属于是灵活运用操作符了,我们一定要多思考。
前置、后置的--、++
好好看清楚a的输出结果。
&取地址和* 间接访问操作符(解引用操作符)
我们已经知道了地址是什么,而&就是取出地址的操作符。
*则是通过指针去访问这个地址住着的人。
int main()
{
int a = 1;
printf("%p\n", &a); //&取地址操作符
int* p = &a;//p是用来存放地址的指针变量
printf("%p\n", p);
//int*代表p是整型指针变量,且p指向的对象类型也是int
*p = 2;
//*解引用操作符,间接访问操作符,通过p找到a的地址
printf("%d\n", a);
*(&a) = 9;
printf("%d\n", a);
//地址应该放到指针变量中
return 0;
}
看清楚它们之间的关系,谁是指针,谁是地址,谁又是住户?
&a是a的地址
p是指针变量
*p和*(&a)代表通过地址访问住户a。
输出结果如下
sizeof和它们组合的应用
void test1(int arr[])
{
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(arr[0]+arr[1]));
printf("%d\n", sizeof(*arr));
//传过来的是数组首元素的地址
//而指针变量是用来存放地址的
//所以形参int arr[]就是int*arr
//在打印首元素地址的大小
}//也就是说传过来的是首元素的地址,然后指针是用来存放地址的
//所以形参int arr[]和int*arr就是一个意思
//你在打印的时候就是在打印首元素地址的size
void test2(char ch[])
{
printf("%d\n", sizeof(ch));
printf("%d\n", sizeof(ch[1]));
printf("%d\n", sizeof(*ch)); //*ch是不包含地址的,只是在通过指针变量ch找到实参ch的地址来访问住在这里的元素
//在打印首元素地址的size,那为什么不是1呢?
// 我们需要知道的是,char ch[]和char*ch是一样的
// 也就是说我们传过来的地址是存放在指针变量中的首元素地址
//而指针变量的大小不是4就是8,这个根据系统不同而定的
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(ch));
char* p = ch;
printf("%d\n", sizeof(*p));
printf("%d\n", sizeof(char*));
printf("%d\n", sizeof(p));
test1(arr);
test2(ch);
//调试
return 0;
}
6·关系操作符
> 大于
>= 大于等于
< 小于
<= 小于等于
!= 用于测试“不相等”
== 用于测试“相等”
注意: 在编程的过程中 == 和 = 所代表的意思不一样!!!
7·逻辑操作符
&&就是一假则假,||就是一真则真。
(真就是非0,假就是0)
&&与&、||与|
&与|是对二进制中的补码进行操作,
而&&与||是对前后表达式的结果进行操作。
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++; //表达式1
//i = a++||++b||d++; //表达式2
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
分别猜测一下&&与注释掉的||表达式的输出结果是什么。
提示:
&&一假则假,碰到一个假的就不再计算。
|| 一真则真,碰到一个真的就不再计算。
什么情况下才会把&&或者||连接的表达式都计算呢?
&&全部为真,或者最后一个为假。
|| 全部为假,或者最后一个为真。
现在我改动一下表达式
可以看到如果一开始为真,那么便不再计算后面的。
8·条件操作符(三母操作符)
exp1 ? exp2 : exp3 exp1是判断条件,为真执行exp2,为假执行exp3
exp就是表达式的意思。
9·逗号表达式
exp1, exp2, exp3, …expN 用逗号隔开的多个表达式。
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("%d\n", c);
int d = 2;
if (a = b + 1, c = a / 2, d > 0)
{
printf("%d\n", a);
printf("%d\n", c);
printf("%d\n", d);
}
}
可以看到,虽然表达式的结果是最后一个,但是需要从左向右计算,即使左边的判断表达式并不成立,依旧会向后计算。
小用途
a = get_val();
count_val(a);
while (a > 0)
{
//其他语句
a = get_val();
count_val(a);
}
这样一段代码可以用逗号表达式优化为
while (a = get_val(), count_val(a), a>0)
{
//其他语句
}
10·下标引用、函数调用和结构成员
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
void test1()
{
}
void test2(const char *str)
{
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello");//实用()作为函数调用操作符。
return 0;
}
struct Stu //结构体
{
char name[10];
int age;
char sex[5];
double score;
};
//函数1
void set_age1(struct Stu stu)
{
stu.age = 18;
}
//函数2
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//指针才可以用->结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20;//结构成员访问
set_age1(stu);
pStu->age = 20;//结构成员访问
set_age2(pStu);
return 0;
}
操作符的学习不止于此,明天写一篇来好好用一下。
写到最后有些累了,没细说,哎,没时间了也是