首页 > 其他分享 >C语言--CH06--操作符(下)

C语言--CH06--操作符(下)

时间:2024-06-01 17:33:50浏览次数:22  
标签:name Stu -- CH06 ++ C语言 int 操作符 printf

C语言–CH06–操作符(下)

四、赋值操作符

1、 赋值和初始化的区别

赋值和初始化有显著的区别

int a = 10; //这是初始化
a = 20; //这是赋值
2、连续赋值

赋值是一种从左往右的运算,并且可以连续赋值:

int a = 0;
int b = 10;
int c = 20;
a = b = c+1;
printf("%d\n",a);

猜一猜a此时的输出为几呢?

推演:

因为赋值运算符是从左往右计算的,所以,首先计算c+1,并把c+1的值赋给b
,之后再把b的值赋给a。即把21的值赋给b,再赋给a。

*赋值运算的连续计算关键记住从左往右的原则

故我们推测输出结果为21。实际运行一下输出为:

21

警告⚠:我们不建议这样写代码,这样写不利于调试,并且可读性差。

3、复合赋值符
+=          %=
-=          &=
*=          |=
/=          >>=
<<=         ^=

相信大家都对这些符号很熟悉了,本文只以+=为例,其他的符号类比

a = a + 10;
a += 10;//两者等价

两者的含义一样,下面更简短。

五、单目操作符

单目操作符即只有一个操作数的操作符,双目操作符有两个操作数
日常所用单目操作符有:

!          //逻辑反
-          //负值
+          //正值
&          //取地址
sizeof     //算操作数的类型长度
~          //对一个数的二进制按位取反
++         //++a,a++
--         //--a,a--
*          //解引用操作符
(类型)	   //强制类型转换
1、!

早期C语言没有bool类型,可以用!来表示真假。我们已经知道,0为假,
非零为真。我们可以写这样一行代码

#include<stdio.h>
int main()
{
	int a = 3;//a不为0,即a为真
	if(a)
	{
		printf("你是个好人\n");
	}
	if(!a)
	{
		printf("你不是个好人\n");
	}
	return 0;
}

这段代码是什么意思呢?一旦a非零,则打印你是个好人,如果a为0,
则a为假,则!a为真,进入第二个if,打印你不是个好人,一次达到bool
类型相同的作用。

2、+ -

这两个符号没什么实际意义。但是还是要讲一下。

int a = -10;
int b = +a;

请问:b = 10还是b = -10?
答案是:-10。
+a并不能改变a的值。

3、取地址操作符&

&是取地址操作符,他的作用就是取出一个变量在内存中的地址。
一般是用一个指针变量来储存地址。

int a = 10;
int* p = &a;
printf("%p",p);
printf("%p",&a);//%p是打印地址时使用

注意:a是一个整型变量,占四个字节,&a取的是第一个字节的地址。
之后的指针环节会详细介绍。

4、sizeof

sizeof有两种用法:(单位是字节Byte)

int a = 10;
int c = sizeof(a);
int n = sizeof(int);
printf("%d \n %d",c,a);
1、计算一个变量所占内存的大小:sizeof(a)
2、计算类型所创建变量所占内存大小:sizeof(int)
3、计算数组的大小

sizeof(数组名)计算的是整个数组的大小

#include<stdio.h>
int main()
{
	int arr[5] = {0};
	printf("%d\n",sizeof(arr));
	
	return 0;
}

输出:

20

整型数组,共5个元素,共20个字节。

sizeof与strlen()有区别:
sizeof不是函数,而是一个单目操作符。能适用于各种类型。
strlen()是函数,只能用来求字符串的长度。

5、二进制按位取反 ~

什么是按位取反?
按位取反即按二进制位取反,是一个整数的补码某一位上为0则变成1,为1则变成0。

如:3的补码为:
00000000000000000000000000000011
按位取反之后为:
11111111111111111111111111111100
因为取反之后也为补码,但是打印出来的是原码,所以类似上一章所讲的:
反码:
11111111111111111111111111111011
原码:
10000000000000000000000000000100
整数:
-4
即~3 = -4

我们来验证一下:

int a = 3;
int c = ~a;
printf("%d\n",c);

输出:

-4

大招:如何把一个二进制的随便一位由0变成1,再把它变回来呢?
我们需要四个武器:按位或|、按位与&、按位取反~、对于数字1的运用。

a = 13
a的补码为:
00000000000000000000000000001101
比如我想要把a的补码中倒数第五位变成1:
00000000000000000000000000011101
怎么做呢?
我们邀请嘉宾————— 1 
1的二进制补码为:
00000000000000000000000000000001
如果1<<4:
00000000000000000000000000010000
再将a按位或(1<<4):
00000000000000000000000000011101
实现了我们想要的操作。此时整数输出为:29

那我们想要变回去怎么办呢?还记得(1<<4)等于多少吗?
00000000000000000000000000010000
如果对(1<<4)按位取反~(1<<4):得到:
11111111111111111111111111101111
再使29按位与~(1<<4):
00000000000000000000000000001101
又回到了13。

我们来验证一下:

#include<stdio.h>
int main()
{
	int a = 13;
	a |= (1<<4);
	int b = a;
	a &= (~(1<<4));
	int c = a;
	printf("%d\n%d",b,c);
	return 0;
}

输出:

29
13
6、++c和c++

直接看例子

int a = 12;
int b = ++a;
printf("%d\n%d",a,b);

输出:

13
13
int a = 12;
int b = a++;
printf("%d\n%d",a,b);

输出

12
13

++a叫做前置++,他的特点是先加后使用。
这里的意思是,先对a进行操作,a+1 = 12 + 1 = 13。
把13这个值给a,再把a的值给b。

a = a + 1
b = a

a++叫做后置++,他的特点是先使用,再加。
这里的意思是,先把a的值给b,即b= 12,再执行b++,即b+1。所以b = 13

b = a
a = a + 1

另一比较难得例子:

#include<stdio.h>
void test(int n)
{
	int b = n+1;
	printf("%d\n",b);
}


int main()
{
	int a = 13;
	int c = 13;
	test(a++);
	test(++c);
	return 0;
}

输出:

14
15

因为test(a++)传的参是a,不是a++。test(++c)传的是c = c + 1。

7、解引用操作符 *

首先看一段代码:

int a = 10;
int* p = &a;
*p = 20;
printf("%d\n",a);

输出:

20

什么意思呢?这里的p是一个指针变量。他相当于a的地址。
而*p相当于访问a地址里的值,即a的值。

*p = 20;就相当于一个指针p,指向了一个房子,*相当于房子的钥匙
*p操作相当于“找到一个房子,并且用钥匙打开门”这一个动作。这个
时候就可以随意更改房子里的东西 。

之后的指针会更详细地讲解。

8、强制类型转换

小学三年级以上都知道3.14是个浮点数。但是一个小学二年级学生写了这样一行抽象的代码:

int a = 3.14;

整型变量怎么能存储浮点数呢?
这个时候,热心大哥哥XXXGOASTDIE决定帮助这个小学二年级学生:我帮他多打了两个符号:

int a = (int)3.14;

这样就变成了一个合法的代码。这种操作叫强制类型转换。即系统默认的double类型3.14被我强制转化为了3.14。这就是强制类型转换。

我们之前学到过生成随机数地函数:

#include<stilib.h>
#include<time.h>
srand((unsigned int)time(NULL));

就用到过强制类型转换。

六、关系操作符

关系操作符很简单,没什么可讲的

>
<
>=
<=
==
!=

只需要注意:
1、==和=别搞混淆了!!!
2、不是什么都可以比较

"abc"== "abcde";

这里比较的只是首元素的地址。
两个字符串是否相等应该用string.h库里的strcmp()来比较。

3、老手都应该注意到if(i==1) if(1 == i)的区别,该链接里有讲到:
if(i==1)和if(1==i)的区别

七、逻辑运算符

&&    逻辑与
||    逻辑或

逻辑与逻辑或和按位与按位或有什么区别?按位与按位或是在二进制层面应用。而逻辑与逻辑或只关注逻辑上的真假。

int a = 5;
int b = 8;
printf("%d\n",a&&b);

输出:

1

因为真值与真值就是真值。

1、法则
a&&b:a和b都为真才真,a和b只要有一个为假就为假。
a||b:a和b只要有一个为真就为真,两个都为假才为假。
2、e.g.判断闰年

闰年如何判断?

能被4整除,且不能被100整除。
或者能被400整除。
#include<stdio.h>
void is_leap_year(int year)
{
	if((year%4 == 0)&&(year%100 != 0)||(year%400 == 0))
	{
		printf("这是闰年.\n");
	}
	else{
		printf("这不是闰年\n");
	}
}


int main()
{
	int n = 0;
	printf("请输入年份:\n");
	scanf("%d",&n);
	is_leap_year(n);

	return 0;
}

这段代码就应用到了逻辑与和逻辑或。

3、360的一道面试题:
#include <stdio.h>
int main()
{
	int i = 0,a = 0,b = 2,c = 3,d = 4;
	i = a++ && ++b && d++;
	printf("%d %d %d %d %d",i,a,b,c,d);

	return 0;
}

请问输出为多少?

0 1 2 3 4

为什么呢?看以下解释:

a++是先用a,再++,所以a刚开始等于0,给i的值也是0,0为假,所以0&&任何值都为假,所以
之后的全为假,都不会执行。所以b、c、d的值都不会变。所以输出0 1 2 3 4。

变式:
第一问:a = 1,结果是什么?
第二问:a = 0,i = a++ || ++b || d++,结果是什么?
解析:

第一问:

a = 1,则把1传给了i,并且左边为真,即要执行++b,b变成了3,为真值,即a++&++b为真	
值,故需要执行d++,即d = 5;真值&真值&真值,所以i最终为1。所以输出为:1 2 3 3 5 

第二问:

首先a把0给i,a为假,a再加一,a为假,则执行++b,++b为真,假||真 = 真,则不要执行d++,
故d = 4,总体来看,假||真||真 = 真,故i = 1.所以输出为:
1 1 3 3 4
4、条件操作符(三目操作符)
表达式1 ? 表达式2 :表达式3
  真        ✔        ×
  假        ×        ✔

只要表达式1为真,则执行表达式2,为假则执行表达式3。

e.g.使用条件表达式求两个数的最大值

int max = 0;
int a = 3;
int b= 4;
max = (a>b ? a : b);

八、逗号表达式

exp1,exp2,exp3....expn;

逗号表达式从左向右依次进行,整个表达式结果为最右边表达式的结果。

int a = 2;
int b = 3;
int c = 4;
int d = (a>b,a = b+10,b = a + 1);

请问d等于多少?

a>b没有改变a,b的值,b+10 = 12,给了a,b= a + 1 = 12 +1 = 14。最后一个表达式
的值为14,给了d,所以d = 14

九、下标引用,函数调用和结构成员操作符

1、下标引用操作符[]

就是访问数组下标的操作符。

int arr[10] = {0};
arr[7] = 8;//这里就是下标引用操作符。

[]的操作数,一个是arr,一个是7。为了证明[]是个操作符,我们有如下操作。
我们都知道加法有交换律,那么[]的两个操作数能交换吗?答案是可以。可以自行验证。

7[arr] = 9;//也能达到跟上面代码一样的效果

证明:arr为首元素地址。arr + 7即为第8个元素的地址。所以*(arr +7)就是第8个元素。
所以:

*(arr+7) = arr[7];
*(arr+7) = *(7+arr) = 7[arr];
2、函数调用操作符()

函数调用操作符不可以省略。放在函数后面,表示调用函数

#include <stdio.h>
void Add(int a,int b)
{
	return a + b; 
}


int main()
{
	int a = 10;
	int b = 20;
	Add(a,b);
	return 0;
}
 

利用函数调用操作符不可以省略的性质,我们也可以知道sizeof不是一个函数。
因为sizeof(a)和sizeof a都是合法的,即sizeof可以省略扩号。

函数调用操作符有1+n个操作数:函数名(1个),内部参数(n个),n = 0合法。
如这里的Add(a,b);有操作数:

Add  a  b
3、结构成员操作符

首先我们定义一个结构体,以学生类型为例:

#include<stdio.h>
#include<string.h>
struct Stu
{
	char name;
	int age;
	double score;
};

包括姓名,年龄,学号。
声明一个Stu类型的变量s,初始化为{0}:

int main()
{
	struct Stu s = {0};
}

我想要设置两个函数,一个放s的值,一个打印s,形参为struct Stu n

void set_value(struct Stu n)
{
	strcpy(n.name,"张三");//不能直接n.name = "张三" ,
						 //因为name是一个地址,不能把字符串直接放进地址
	n.age = 20
	n.score = 2022105022.0;
}


void print_value(struct Stu n)
{
	printf("%s %d %lf\n",n.name,n.age,n.score);
}

之后我需要调用这两个函数,写在main()里。这里我汇总一下:

#include<stdio.h>
#include<string.h>


struct Stu
{
	char name[20];
	int age;
	double score;
};


void set_value(struct Stu n)
{
	strcpy(n.name,"张三");//不能直接n.name = "张三" ,
						 //因为name是一个地址,不能把字符串直接放进地址
	n.age = 20;
	n.score = 2022105022.0;
}


void print_value(struct Stu n)
{
	printf("%s %d %lf\n",n.name,n.age,n.score);
}


int main()
{
	struct Stu s = {0};
	set_value(s);
	print_value(s);
	return 0;
}

输出:

0 0.000000

输出的跟我们想象的不一样,是传参的问题:注意:形参是实参的一次拷贝。我们
定义了一个叫s的Stu类型的变量,开辟了一个空间,创建了实参n,拷贝了形参s,
并在拷贝的区域储存了值,之后返回的值并不是n的而是,s的,此时s的内存里还是
原来的值,所以打印出的都是0。所以我们要取s的地址传进去而不是把s传进去:

set_value(&s);

取地址就需要用指针变量接收,所以我们要把n改成指针变狼,并对set_value内部更改:

void set_value(struct Stu* n)
{
	strcpy((*n).name,"张三");//不能直接n.name = "张三" ,
						 //因为name是一个地址,不能把字符串直接放进地址
	(*n).age = 20;
	(*n).score = 2022105022.0;
}

这样才是正确的。但是这样会不会麻烦了一点,有没有更简单的方法呢?
有:用结构体成员操作符:->

void set_value(struct Stu* n)
{
	strcpy(n -> name,"张三");//不能直接n.name = "张三" ,
						 //因为name是一个地址,不能把字符串直接放进地址
	n -> age = 20;
	n -> score = 2022105022.0;
}

这是个更简单的方法。总结在一起:

#include<stdio.h>
#include<string.h>


struct Stu
{
	char name[20];
	int age;
	double score;
};


void set_value(struct Stu* n)
{
	strcpy(n -> name,"张三");//不能直接n.name = "张三" ,
						 //因为name是一个地址,不能把字符串直接放进地址
	n -> age = 20;
	n -> score = 2022105022.0;
}


void print_value(struct Stu n)
{
	printf("%s %d %lf\n",n.name,n.age,n.score);
}


int main()
{
	struct Stu s = {0};
	set_value(&s);
	print_value(s);
	return 0;
}

输出:

张三 20 2022105022.000000

(*p).age 跟p->age是一样的

当然,我们这里打印的还是n内存里的数据,我们想要直接打印s的数据,可以对print_value做同样的操作:

#include<stdio.h>
#include<string.h>


struct Stu
{
	char name[20];
	int age;
	double score;
};


void set_value(struct Stu* n)
{
	strcpy(n -> name,"张三");//不能直接n.name = "张三" ,
						 //因为name是一个地址,不能把字符串直接放进地址
	n -> age = 20;
	n -> score = 2022105022.0;
}


void print_value(struct Stu* n)
{
	printf("%s %d %lf\n",n->name,n->age,n->score);
}


int main()
{
	struct Stu s = {0};
	set_value(&s);
	print_value(&s);
	return 0;
}

标签:name,Stu,--,CH06,++,C语言,int,操作符,printf
From: https://blog.csdn.net/2401_83411974/article/details/139361047

相关文章

  • 高度向稀疏的毫米波成像方法
    分析:高度向稀疏的采样数据是整行缺失的,每一列上都缺失了大量数据,这可能造成高度向的混叠现象,但是每一行上是没有缺失数据的,这说明方位向应该是不会出现混叠现象。考虑到上述现象,对于三维成像,可不可以先实现距离-方位向的二维成像,而后再实现高度向的聚焦,进而实现三维成像。想......
  • C语言--CH05--操作符(上)
    C语言–CH05–操作符(上)一、算术操作符+-*/%操作规则:1、%的操作数只能为整数。2、/的操作数中只要有一个浮点数,运算结果就为浮点数。3、除了%以外的操作符的操作数都可以不是整数。4、%10即取个位数,%100即取个位十位数,以此类推。5、整数/整数,结果向下取整......
  • 【C++】内存管理
    文章目录1.回顾C/C++的内存管理2.C++内存管理方式2.1new/delete对于内置类型2.2new/delete对于自定义2.3operatornew与operatordelete函数2.4new和delete的实现原理2.5定位new表达式3.常见面试题1.回顾C/C++的内存管理首先,我们来回顾一下内存中的区域划分......
  • css42 CSS Units
    https://www.w3schools.com/css/css_units.asp CSSUnitsCSShasseveraldifferentunitsforexpressingalength.ManyCSSpropertiestake"length"values,suchaswidth,margin,padding,font-size,etc.Lengthisanumberfollowedbyalengthun......
  • 【MATLAB】概述1
    非~注释%定义>>数组赋值赋值:>>x=1函数数组x=[x1,x2]行向量(,or)x=[x1;x2]列向量x.'转置等间隔向量1-10向量:>>x=linspace(1,10,10)矩阵矩阵:>>A=[1,2,3;4,5,6;7,8,9]x=A(:)转置行列:>>size(C)查看矩阵的行列数D=A+BC=A*BorA.*D访问数......
  • java8,频繁old gc,通过修改g1回收器之后,效果很好
    原配置:-Xms15360m-Xmx15360m-XX:NewSize=4096m-XX:MaxNewSize=4096m-XX:MetaspaceSize=512m-XX:MaxDirectMemorySize=1024m-XX:+UseG1GC-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath=/data/logs/skynet-\${DAOKEAPPUK}/\${DAOKEAPPUK}_heapDump.hprof-XX:+UseC......
  • 二叉树链式结构的前序、中序、后序、层序遍历
    文章目录一、二叉树创建二、前序遍历概念以及解释代码三、中序遍历概念及解释代码四、后序遍历概念及解释代码五、层序遍历概念及解释代码一、二叉树创建&mesp; 实现二叉树的遍历,我们要先手搓出一个二叉树,在次基础上实现二叉树的前序、中序、后序、层序遍历。......
  • AtCoder Beginner Contest 355 (E,F)
    总结:这把B都错题了一直Wa,然后队友告诉我说F貌似可做,写了半个小时F发现题目读假了,于是四题下班。E-GuesstheSum题目大意:给定一个隐藏的、长度为N的数组,下标从0开始,题目给定L,R,要你用最少的询问次数求出\(\sum_{i=L}^{R}a_{i}\)。对于每次询问,可以选择一个i和j,然......
  • 在不受支持的 Mac 上安装 macOS Sonoma (OpenCore Legacy Patcher v1.5.0)
    在不受支持的Mac上安装macOSSonoma(OpenCoreLegacyPatcherv1.5.0)InstallmacOSonunsupportedMacs请访问原文链接:https://sysin.org/blog/install-macos-on-unsupported-mac/,查看最新版。原创作品,转载请保留出处。作者主页:sysin.org历时两个月,OpenCoreLegacyPat......
  • ARM-V9 RME(Realm Management Extension)系统架构之系统安全能力的信任根服务
    安全之安全(security²)博客目录导读目录一、信任根服务1、非易失性存储2、根看门狗3、随机数生成器4、加密服务5、硬件强制安全性本节定义了系统架构必须支持的一般安全属性和能力,以确保RME安全性。本章扩展了可能属于系统认证配置文件的一部分的其他安全规范,例如基......