2024/5/23 学习杂记

目录

位运算与逻辑运算读程序练习

[在switchcase 语句中能否使用continue关键字?为什么?](#在switchcase 语句中能否使用continue关键字?为什么?)

为什么尽量不使用goto语句?

void

i++与++i

[i++和++i 哪个效率更高?](#i++和++i 哪个效率更高?)

良好的条件比较语句风格

memcpy

memset


位运算与逻辑运算读程序练习

int x = 3, y, z;

y = z = 2;

12  x = (y & z); printf("x = %d\n", x);

13  x = (y && z); printf("x = %d\n", x);

y = 4;

16  x = (y | z); printf("x = %d\n", x);

17  x = (y || z); printf("x = %d\n", x);

程序执行至第12 行时,y和z 的值都为2。此行把y和z 做按位与(&)运算的结果赋给变量x。y 和z 的二进制都是10,因此y & z 的结果为二进制10。因此x的值为2。

程序执行至第13 行时,y和z 的值都为2。此行把y和z 做逻辑与(&&)运算的结果赋给变量x。此时y和z 的值都不是0,因此y && z 的结果为1。因此x 的值为1。

程序执行至第16 行时,y的值为4,z 的值为2。此行把y和z 做按位或(|)运算的结果赋给变量x。此时y和z的二进制表示分别为100和010,因此y|z的结果为110。因此x的值为110,十进制表示为6。

程序执行至第17 行时,y 的值为4,z 的值为2。此行把y 和z 做逻辑或(||)运算的结果赋给变量x。此时y和z的值都不是0,因此y||z的结果为1。因此x的值为1。

在switchcase 语句中能否使用continue关键字?为什么?

switch-case语句中通常不能直接使用continue关键字。continue语句通常用于循环语句(如forwhile循环),用于跳过当前迭代并继续下一次循环迭代。

如果switch语句位于某个循环内部,那么continue将会作用于那个外层循环,跳过循环体的剩余部分并进入下一次迭代。如果switch不在循环内部,则continue关键字在技术上虽不产生语法错误,但也没有实际用途,因为没有迭代可跳过。

为什么尽量不使用goto语句?

  1. 可读性降低:goto语句使得程序的控制流变得难以追踪,阅读者需要跟随goto跳转来理解程序的执行顺序,这增加了理解难度,尤其是当程序规模增大时。

  2. 结构混乱:goto破坏了程序的结构化编程原则,如顺序、分支(if-else、switch)、循环(for、while、do-while)等,这些结构提供了清晰的逻辑控制流控制方式。过度依赖goto可能导致"意大利面条代码",难以维护。

  3. 维护困难:goto使得修改和维护代码变得复杂,因为修改一处可能需要考虑对跳跃目标的影响,这可能导致连锁反应式的修改,增加了出错风险。

  4. 调试挑战:在调试时,goto使得堆栈跟踪和理解程序的执行路径复杂化,调试工具可能也难以准确展示程序流程。

  5. 团队协作:在团队开发环境中,使用goto可能让其他成员难以理解代码意图,增加协作难度,不利于代码评审和交接。

  6. 现代替代方案:现代编程语言和实践提供了丰富的控制结构、函数、类、异常处理机制、迭代器等,能够有效替代goto达到相同目的,同时保持代码清晰和模块化。

在某些特定场合(如低级别编程、操作系统内核、嵌入式系统或某些优化特定算法)goto可能有其正当使用场景,但总体上,遵循结构化编程原则,限制或避免goto使用是提升代码质量和可维护性的共识。

void

如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值; 如果p1和p2指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的 指针类型转换为左边指针的类型。

例如:

float *p1;

int *p2;

p1 = p2;

其中p1=p2语句会编译出错,提示"'=' :cannotconvertfrom'int*' to 'float*'",必须改为: p1 = (float *)p2;

而void*则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换:

void *p1;

int *p2;

p1 = p2;

但如果

void *p1;

int *p2;

p2 = p1;

就会提示"'=' : cannotconvertfrom 'void *' to 'int *'"。

必须写成 p2 = (int *)p1

i++与++i

cpp 复制代码
#include <stdio.h>
int main(void)
 {
  int i=8;

  printf("%d\n",++i);
  printf("%d\n",--i);
  printf("%d\n",i++);
  printf("%d\n",i--);
  printf("%d\n",-i++);
  printf("%d\n",-i--);
  printf("------\n");

  return 0;
 }

求输出内容。

程序的说明如下:

程序第7 行,此时i 的值为8。这里先i 自增1,再打印i 的值。因此输出9,并且i的值也变为9。

程序第8 行,此时i 的值为9。这里先i 自减1,再打印i 的值。因此输出8,并且i的值也变为8。

程序第9 行,此时i 的值为8。这里先打印i 的值,再i 自增1。因此输出8,并且i的值也变为9。

程序第10 行,此时i 的值为9。这里先打印i 的值,再i 自减1。因此输出9,并且i的值也变为8。

程序第11 行,此时i 的值为8。这里的"-"表示负号运算符。因此先打印-i 的值,再i自增1。因此输出-8,并且i的值也变为9。

程序第12 行,此时i 的值为9。这里的第一个"-"表示负号运算符,后面连在一起的两个"-"表示自减运算符。因此先打印-i的值,再i自减1。因此输出-9,并且i的值也变为8。

i++和++i 哪个效率更高?

内建数据类型的情况,效率没有区别。

自定义数据类型的情况,++i效率较高。

i++(后置自增)和++i(前置自增)在大多数现代编译译器下对于基本数据类型(如int、float、double等)来说,性能上几乎没有区别。这两种操作最终生成的汇编译代码很可能是相同的,尤其是在优化级别较高的编译译条件下。

在C++中:

•后置自增i++会先返回i的当前值,然后将i的值加1。

•前置自增++i会先将i的值加1,然后返回加1之后的值。**理论上讲,如果值不需要被保留,使用++i可能略微高效,因为它直接计算并返回新值,而i++可能涉及一个临时变量来存储旧值。**但在实际应用中,这种差异微乎其微,几乎不影响程序的整体性能,特别是在考虑现代处理器的速度和编译器优化技术。

在具体应用时,选择i++还是++i更多基于语境和语义的清晰性,而非性能考虑。

如果增量操作的结果是赋值操作的一部分,使用++i更自然;

如果需要先使用原值后递增,则用i++。

良好的条件比较语句风格

假设浮点变量的名字为 x,它与 0.0的比较如下。

第一种:

1 if (x == 0.0)

2 if (x != 0.0)

第二种:

1 if ((x >= -EPSINON) && (X <= EPSINON))

2 if ((x < -EPSINON) || (X > EPSINON))

其中,EPSINON是允许的误差(精度)。

第二种风格较良好。

注意:无论是float 还是double 类型的变量,都有精度限制。所以一定要避免将浮点变量用"=="或"!="与数字比较,应该设法转化成">="或"<="形式。

假设布尔变量名字为flag,它与零值比较的标准if语句如下。

第一种:

1 if (flag == TRUE)

2 if (flag == FALSE)

第二种:

1 if (flag)

2 if (!flag)

第二种风格较良好。根据布尔类型的语义,零值为"假"(记为 FALSE),任何非零值都是"真"(记为TRUE)。TRUE的值究竟是什么并没有统一的标准。例如Visual C++将TRUE定义为1,而Visual Basic 则将TRUE 定义为-1。因此不可将布尔变量直接与TRUE、FALSE进行比较。

假设整型变量的名字为value,它与零值比较的标准if语句如下。

第一种:

1 if (value == 0)

2 if (value != 0)

第二种:

1 if (value)

2 if (!value)

第一种风格较良好,第二种风格会让人误解value 是布尔变量,应该将整型变量用"=="或"!="直接与0比较。

memcpy

cpp 复制代码
void * memcpy(void *dest,constvoid *src,size_t len);

用于将一块内存区域的数据从源地址复制到目的地址

每个参数的含义如下:

•void *dest:指向复制内容将要存放的目标数组的指针。目标区域必须有足够的空间来容纳复制的内容。

•const void *src:指向要复制内容的源数组的指针。

•size_t len:从源到目标复制的字节数。

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char src[50] = "Hello, World!";
    char dest[50]; // 目标量足够大以容纳src的内容

    // 使用memcpy函数复制src数组的内容到dest
    memcpy(dest, src, src, strlen(src) + 1); // 注意加1是因为要复制字符串末尾部的'\0''

    printf("Original String: %s\n", src);
    printf("Copied String: %s\n", dest);

    return 0;
}

在长度上加1是为了确保字符串的终止符也被复制,因为strlen不计算字符串的终止符'\0'字符

输出结果

cpp 复制代码
Original String: Hello, World!
Copied String: Hello, World!

memset

cpp 复制代码
void *memset(void *buffer, int c, size_t num, size_t size);

memset函数会将从buffer指向的内存地址开始的连续size个字节(字节)全部设置为c指定的值。这个函数常用于清零内存块(当c设为0时),或者快速填充固定值。

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    int arr[10];
    memset(arr, 0, sizeof(arr)); // 将整个arr数组清零
    printf("Array after memset: %d\n", arr[0]); // 输出应为0,因为所有元素都被清零了
    return 0;
}
相关推荐
K3njuan7 分钟前
《数据结构》学习系列
学习
结衣结衣.9 分钟前
C++ 类和对象的初步介绍
java·开发语言·数据结构·c++·笔记·学习·算法
limengshi1383922 小时前
通信工程学习:什么是RIP路由信息协议
网络·网络协议·学习·智能路由器·信息与通信
xiaobuding_QAQ3 小时前
自用Proteus(8.15)常用元器件图示和功能介绍(持续更新...)
单片机·嵌入式硬件·学习·proteus
wei_shuo4 小时前
偏标记学习+图像分类(论文复现)
学习·分类·数据挖掘
Miqiuha5 小时前
lock_guard和unique_lock学习总结
java·数据库·学习
一 乐5 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
加油,旭杏6 小时前
【中间件学习】fastCG介绍和使用
学习·nginx·fastcgi
limengshi1383927 小时前
通信工程学习:什么是TFTP简单文件传输协议
网络·网络协议·学习·信息与通信
GFCGUO7 小时前
ubuntu18.04运行OpenPCDet出现的问题
linux·python·学习·ubuntu·conda·pip