软件知识点零散总结

提示:这边是之前整理的软件知识点,一起整理在博客。

之前是用word文档整理的,然后因为保存啊,清理文件啊,或者换公司啊都弄丢了,感觉不如线上的好管理,所以整理在csdn上


前言

例如:这次的知识点包括break的使用,c++相关,以及stm32的内存地址分配相关。

补充牛客c语言知识点整理:C语言教程_编程入门教程_牛客网


一、关于break

C 语言中的 continue 语句有点像 break 语句。但它不是跳出该循环语句continue是跳过本次循环直接开始下一次循环的。

在for循环里,continue会跳过本次循环,但是自增语句仍然会执行,

而在while和do…while语句里则是跳过循环重新执行判断语句

二、C++中static_cast和 reinterpret_cast 的区别

C++primer 第五章里写了 编译器 隐式执行任何类型转换都可由static_cast显示完成;

reinterpret_cast通常为操作数的位模式提供较低层的重新解释

1.static_cast

C++中的static_cast执行非 多态 的转换,用于代替C中通常的转换操作。因此,被做为隐式类型转换使用。比如:

代码如下(示例):

int i;
float f = 166.7f;
i = static_cast<int>(f);

//此时结果,i的值为166。

2.reinterpret_cast

C++中的reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓通常为操作数的位模式提供较低层的重新解释也就是说将数据以二进制存在形式的重新解释。比如:

int i;
char *p = "This is a example.";
i = reinterpret_cast<int>(p);

此时结果,ip的值是完全相同的。reinterpret_cast的作用是说将指针p的值以二进制(位模式)的方式被解释为整型,并赋给i//i 也是指针,整型指针;一个明显的现象是在转换前后没有数位损失。

3.mak_shared

 make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr;由于是通过shared_ptr管理内存,因此一种安全分配和使用动态内存的方法。

2023年11月30日15:51:54更新,之前这边不太清楚,今天了解了一些,还引入两个新的概念:上行转换和下行转换。具体看这篇帖子。

第十七章_类型转换_上行转换和下行转换_渭穹之江的博客-CSDN博客

实际上代码规范都是推荐避免使用const_cast常量转换和reinterpret_cast转换。

2023年12月15日16:42:35

C++ static_cast、dynamic_cast、const_cast和reinterpret_cast(四种类型转换运算符)

4、2024年2月18日16:30:30更新

增加dynamic_cast转换

称为上下行转换,用于子类和父类之间的转换

三、字节序

1.题目1

在x86机器上输出多少?

char a[5] = "1234";
int *p = (int*)a;
printf("%x\n",*p);  
int sample = 0x12345678;          


//34333231

首先针对这个题目,有几个知识点是需要确定的。①x86是小端模式(低位在低地址)②1234的ascii码对应的十六进制为31H、32H...,第三个就是数据在内存中的具体存储情况了。如下图

 

根据图中显示,一个以十六进制的数据存在x86内存中,0x12345678的存储是地位存在低地址,字符串在内存中的存储是31 32 33 34,转成int型的数据就是0x34333231了。即代码中的显示情况。 

四、结构体

结构体字节大小

1、结构体包含结构体

第一种情况:

1、以下程序的输出结果是 (单选题)
 
int main()
{
    struct Student1
    {
        char name[3];
        struct Info
        {
            int age;
            int gender;
        }info;
    };

    printf("struct:%zu\r\n", sizeof(struct Student1));

    return 0;
}

//打印结果 struct:12

第二种情况:

1、以下程序的输出结果是 (单选题)
 
int main()
{
    struct Student1
    {
        char name[3];
        struct Info
        {
            int age;
            int gender;
        };
    };

    printf("struct:%zu\r\n", sizeof(struct Student1));

    return 0;
}

//打印结果 struct:3

2、多种数据类型

int main()
{   
    struct Student2
    {
        char name[10];
        int age;
        char gender;
        float score[4];
    };
    printf("struct:%zu\r\n", sizeof(struct Student1));

    return 0;
}

//打印  struct:36

2023年11月17日11:12:32

float占四个字节

结构体中有float数据类型

int main()
{
    struct pack {
       int i;
       short s;
       double d;
       char c;
       short f;
    };

    printf("size:%d",sizeof(pack));
}

//打印 size:24

windows则是以最大的内置类型的字节数对齐,在结构体内最大的内置类型为double,其大小为8个字节。他们在内存中的对齐方式。


2、结构体定义和初始化

1、第一种方法

       (结构体名)
struct Stu1
{
    char name[10];
    int age;
}stu1,stu2;
(结构体变量)

int main()
{
    strcpy(stu1.name, "lili");
    stu1.age = 12;
    strcpy(stu2.name, "tom");;
    stu2.age = 15;

    printf("%s\n",stu1.name);
    printf("%d\n",stu1.age);
    printf("%s\n",stu2.name);
    printf("%d\n",stu2.age);


    struct Stu stu3;
    strcpy(stu3.name, "jack");;
    stu3.age = 11;

    printf("%s\n",stu3.name);
    printf("%d\n",stu3.age);
}

//    lili
//    12
//    tom
//    15
//    jack
//    11

结构体定义方式一样,初始化方式不同

struct Stu2
{
    char name[10];
    int age;
};

int main()
{
    struct Stu2 stu1 = {"lili", 12};

    struct Stu2 stu2 = {"tom", 15};

    printf("%s\n",stu1.name);
    printf("%d\n",stu1.age);
    printf("%s\n",stu2.name);
    printf("%d\n",stu2.age);


    struct Stu2 stu3 = {"jack", 11};;

    printf("%s\n",stu3.name);
    printf("%d\n",stu3.age);
}

//    lili
//    12
//    tom
//    15
//    jack
//    11

省略结构体名,但是必须要有变量名

struct
{
    char name[10];
    int age;
}stu1, stu2;

int main()
{
    strcpy(stu1.name, "lili");
    stu1.age = 12;

    strcpy(stu2.name, "tom");;
    stu2.age = 15;

    printf("%s\n",stu1.name);
    printf("%d\n",stu1.age);
    printf("%s\n",stu2.name);
    printf("%d\n",stu2.age);

    return 0;
}

//    lili
//    12
//    tom
//    15
//    jack
//    11

2、第二种方法 typedef

               (结构体名可以省略)
typedef struct Stu
{
    char name[10];
    int age;
}stu, *pstu;

int main()
{
    stu stu1 = {"lili", 12};
    printf("%s\n",stu1.name);
    printf("%d\n",stu1.age);

    stu stu2;
    strcpy(stu2.name, "tom");
    stu2.age = 15;
    printf("%s\n",stu2.name);
    printf("%d\n",stu2.age);
    
    //pstu stu3或者 pstu stu3 = NULL异常
    pstu stu3 = (pstu)malloc(sizeof(stu));
    strcpy(stu3->name, "kack");
    stu3->age = 15;

    printf("%s\n",stu3->name);
    printf("%d\n",stu3->age);

    return 0;
}

//lili
//12
//tom
//15
//kack
//15

3、pack

下面是关于pack用法

首先引入一个实例

int main()
{
    struct pack1 {
        int e;
        char f;
        short a;
        char b;
    };

    struct pack2 {
        int e;
        short a;
        char f;
        char b;
    };

    printf("size:%d\n",sizeof(pack1));
    printf("size:%d\n",sizeof(pack2));
}

//打印错误
//size:12
//size:10

打印结果有误

//2023年11月17日11:25:31更新
//打印正确
//size:12
//size:8

之前看这个算错了就很懵,然后就查原因。最后发现这个跟偏移量有关。在这个pack相关知识之前还是不懂的,现在搞清楚了。关于偏移量后面会写。


#pragma pack(n)

先举个关于#pragma pack()的用法实例

int main()
{
    #pragma pack(1)
    struct pack1 {
        int e;
        char f;
        short a;
        char b;
    };
    #pragma pack() //取消自定义字节对齐方式  一开始写成#pragma pack,导致下面的结构体也是8个字节

    struct pack2 {
        int e;
        char f;
        short a; 
        char b;
    };

    printf("size:%d",sizeof(pack1));
    printf("size:%d",sizeof(pack2));
}

//打印 
//size:8
//sie:12

平时pack用法写的少,都不知知道怎麽写,写个规范写法。


解释一:

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

如果n大于结构体最大成员长度,pack(n)无效,按照默认对齐方式对齐。

规则:

  1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

  2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

n 字节的对齐方式 VC 对结构的存储的特殊处理确实提高 CPU 存储变量的速度,但是有时候也带来 了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。 VC 中提供了#pragma pack(n)来设定变量以 n 字节对齐方式。n 字节对齐就是说 变量存放的起始地址的偏移量有两种情况:

第一、如果 n 大于等于该变量所占用的字 节数,那么偏移量必须满足默认的对齐方式。

第二、如果 n 小于该变量的类型所占用 的字节数,那么偏移量为 n 的倍数,不用满足默认的对齐方式。结构的总大小也有个 约束条件,分下面两种情况:如果 n 大于所有成员变量类型所占用的字节数,那么结 构的总大小必须为占用空间最大的变量占用的空间数的倍数; 否则必须为 n 的倍数。

下面举例说明其用法。 #pragma pack(push) //保存对齐状态

 #pragma pack(4)//设定为 4 字节对齐

struct test { char m1; double m4; int m3; }; #pragma pack(pop)//恢复对齐状态 以上结构体的大小为 16:

下面分析其存储情况,首先为 m1 分配空间,其偏移量 为 0,满足我们自己设定的对齐方式(4 字节对齐),m1 大小为 1 个字节。接着开始 为 m4 分配空间,这时其偏移量为 1,需要补足 3 个字节,这样使偏移量满足为 n=4 的倍数(因为 sizeof(double)大于 4),m4 占用 8 个字节。接着为 m3 分配空间,这时 其偏移量为 12,满足为 4 的倍数,m3 占用 4 个字节。这时已经为所有成员变量分配 了空间,共分配了 16 个字节,满足为 n 的倍数。如果把上面的#pragma pack(4)改为 #pragma pack(8),那么我们可以得到结构的大小为 24。

大家看了这些文字描述头也一定会发麻吧,我坚持读完后,然后自己编写了一个程序:

#pragma pack(4)

struct node{undefined

  int e;
  char f;
  short int a;
  char b;

};

struct node n;

printf("%d\n",sizeof(n));

我自己算的结果是16,结果实际结果是:12

然后结构体内部数据成员变动一下位置:

#pragma pack(4)

struct node{undefined

  char f;
  int e;
  short int a;
  char b;};

struct node n;

printf("%d\n",sizeof(n));

实际结果是:12

将对齐位数强制定位2

#pragma pack(2)

struct node{undefined

  char f;
  int e;
  short int a;
  char b;};

struct node n;

printf("%d\n",sizeof(n));

实际结果是:10

将对齐位数强制定位1

#pragma pack(1)

struct node{undefined

  char f;
  int e;
  short int a;
  char b;};

struct node n;

printf("%d\n",sizeof(n));

实际结果是:8

看着输出结果和文字描述有点晕,下面简单说一下俺的判定规则吧:

其实之所以有内存字节对齐机制,就是为了最大限度的减少内存读取次数。我们知道CPU读取速度比内存读取速度快至少一个数量级,所以为了节省运算花费时间,只能以牺牲空间来换取时间了。

下面举例说明如何最大限度的减少读取次数。

#pragma pack(1)

struct node{undefined

  char f;
  int e;
  short int a;
  char b;};

struct node n;

printf("%d\n",sizeof(n));

这里强制按照1字节进行对齐,可以理解成所有的内容都是按照1字节进行读取(暂且这样理解,因为这样可以很好的理解内存对其机制),其他所有的数据成员都是1字节的整数倍,所以也就不用进行内存对其,各个成员在内存中就按照实际顺序进行排列,结构体实际长度为8

#pragma pack(2)

struct node{undefined

  char f;
  int e;
  short int a;
  char b;};

struct node n;

printf("%d\n",sizeof(n));

这里强制按照2字节进行对齐。如果内存分布仍然是连续的话,那么int e就得三次才能读到CPU中,所以为了“讲究”int e的读取,所以在char f之后预留1BYTE,最后的char b也是如此,所以长度为10

#pragma pack(4)

struct node{undefined

  char f;
  int e;
  short int a;
  char b;};

struct node n;

printf("%d\n",sizeof(n));

这里强制按照4字节进行对齐。所以char f后要预留3BYTE,而short int a 和 char b可以一次读取到CPU(按照4字节读取),所以长度为12

如果#pramga pack(n)中的n大于结构体成员中任何一个成员所占用的字节数,则该n值无效。编译器会选取结构体中最大数据成员的字节数为基准进行对其


下面介绍关于偏移量的相关知识

1:结构体大小必须是成员类型大小的公倍数;
2:结构体的大小不是所有成员大小简单的相加,需要考虑到系统在存储结构体变量时的地址对齐问题;
3:偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体大小等于最后一个成员的偏移量加上最后一个成员的大小;

引入一个例子

#include<bits/stdc++.h>
using namespace std;
int main()
{
	struct test
	{
		int a;
		int b;
		int c;
		double d;
	};
	cout<<sizeof(test);
 }

输出结果为 24 而非20 这个例子说明了结构体的大小并非成员类型的简单相加,那么到底怎么回事呢?我们接着往下看:

1在默认情况下,各成员变量存放的起始地址相对于结构的起始地址的偏移量为为成员变量类型大小或其倍数;
2结构体总大小为成员类型大小的公倍数
3结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
4为什么要对齐?
CPU在一个时钟周期内存取数据,效率高(咱也听不懂)

关于偏移量的第一个例子:

#include<bits/stdc++.h>
using namespace std;
int main()
{
	struct test
	{
		int a;
		int b;
		int c;
		double d;
	};
	cout<<sizeof(test);
 }

//打印 24

结构体中第一个变量a的地址就是结构体的首地址,显然a偏移量=0;
第二个变量b的偏移量为a偏移量+a的类型的大小,易得b偏移量=4;
同理,c偏移量8,d偏移量为12;
此时,d的偏移量不为double类型大小的整数倍,因此在c后面补上4个空字节,此时d偏移量为16;
结构体大小=最后一个成员偏移量+最后一个成员类型大小;
因此,该例中结构体大小为24;


2023年6月30日17:51:58  更新

#pragma pack(2)

class A
{
    int i;
    union U
    {

        char buf[13];
        int i;
    }u;
    void foo(){}
    typedef char* (*f)(void*);
    enum{red, green, blue} colour;
}aClass;

//sizeof(a) = 22
//去除#pragma pack(2)后,sizeof(a) = 24

4、含有指针成员的结构体

参考文章链接如此:

【精选】C语言结构体里的成员数组和指针_Y_YoungSun的博客-CSDN博客

带指针成员的结构体初始化方式如下:

struct line {

    int length;

    char *contents;

};

int main(){

    int this_length=10;

    struct line *thisline = (struct line *)malloc (sizeof (struct line));

    thisline->contents = (char*) malloc( sizeof(char) * this_length );

    thisline->length = this_length;

    memset(thisline->contents, 'a', this_length);

    printf(thisline->contents);

    return 0;

}

//aaaaaaaaaaeBlock?脥
//没有结束符\n

这个帖子还提及一个知识点就是Flexible array,柔型数组。代码如下

#include <stdlib.h>

#include <string.h>

struct line {

    int length;

    char contents[0]; // C99的玩法是:char contents[]; 没有指定数组长度

};

int main(){

    int this_length = 10;

    struct line *thisline = (struct line *)

    malloc (sizeof (struct line) + this_length);

    thisline->length = this_length;

    memset(thisline->contents, 'a', this_length);

    printf(thisline->contents);

    printf("thisline:%p\n", thisline);

    printf("*thisline:%d\n", *thisline);

    printf("thisline->length:%p\n", &thisline->length);

    printf("thisline->contents:%p\n", &thisline->contents);

    printf("sizeof(struct line):%d\n", sizeof(struct line));

    return 0;

}


//aaaaaaaaaa
//thisline:00ED1A68
//*thisline:10
//thisline->length:00ED1A68
//thisline->contents:00ED1A6C
//sizeof(struct line):4

关于这段代码中需要特别指出的是:

一、结构体的地址就是结构体第一个成员的地址。结构体对成员的访问就是地址偏移。

二、sizeof含有柔性数组的结构体的大小就是除了柔型数组其他成员的大小。


  其实这个问题还有一个没有认识清晰,之前一直留在心里,像根刺,源于一次考试当时不敢随便操作。这个又想起所以查一下资料,逐渐清晰。

结构体含有指针成员的情况,不仅需要给结构体申请内存,还需要要指针申请内存。

这个帖子讲的就是这个问题,两个结构体能直接赋值吗?_两个结构体可以直接赋值吗-CSDN博客

但是他没有说怎么处理。在C++里面就在类中构造拷贝构造函数,但是C++结构体怎么处理呢?

c语言怎么处理呢?

c语言结构体直接赋值-C语言-E安全

这个帖子说c语言中带指针的成员不能相互赋值 。


2023年12月18日17:49:47

这边还遇到一个题目,那就是这个题目

牛客网公司真题_免费模拟题库_企业面试|笔试真题

Which of the following can’t be used in memset?(多选)答案AD

A、struct Parameters { int x; std::string str_x; };

B、struct Parameters { int x; int* p_x; };

C、struct Parameters { int x; int a_x[10]; };

D、class BaseParameters{ public: virtual void reset() {} }; class Parameters : public BaseParameters{ public: int x; };

我选错为BC。

解释如下:A选项string对象中分配的堆内存,保存在string成员变量中,被memset后,该部分内存无法释放; D选项,类对象中有vptr指针,用memset会连vptr指针的值也清除。

至于BC,我认为也是有问题的啊,但是什么原因BC是对的未知。

但是评论中有个链接需要注意一下:关于struct 结构体与memset的狗血教训_memset struct-CSDN博客

链接中说到:

1、类中含有C++类型的对象(string, list, set, map等)时,千万不要使用memset进行初始化,因为会破坏对象的内存,可用构造函数来实现。所以A错。

2、类含有指针时,初始化时,并不会初始化指针指向对象的值,而会把该指针设置为0;

3、类含有虚函数表时,初始化会破坏虚函数表,后续对虚函数的调用都将出现异常;D选项应该是有虚表才不能用memset吧

以上,对于在结构体中有指针,怎么初始化,查到这样一篇帖子

memset()函数与memcpy()函数知识总结---结构体中有指针变量_memcpy内存泄漏-CSDN博客

里面陈述了正确的方法:

当结构体里面存着指针对象时,直接使用memset()函数初始化结构体,指针成员会被初始化为0即空指针。

1.正确的初始化操作是先对结构体中执行memset(也可逐项初始化),然后对的指针成员分配空间,最后单独初始化该指针指向的空间。
2.若在后续使用过程中需要对结构体对象置初始值,也最好不要直接memset,否则指针成员会被写null,导致指针成员无法指向指向原来正确的内存地址导致内存泄漏。正确的做法是逐个清零非指针成员,释放指针成员指向的内存,置空指针成员或者仅清空指针指向的内存,不修改指针成员。

5、其他小知识点

1、结构体需要定义在头文件中

2、不写typedef时需要保持struct xxx形式,比如定义一个结构体对象:struct Obj obj;

五、字节序

首先是关于大小端,x86是小端模式

下面是关于字节序的一个例题

题目一:

在X86下,有下列程序 
    #include <stdio.h>
    void main()
    { 
        union
        { 
            int     k;
            char    i[2];
        }*s,a;
        s = &a;
        s->i[0] = 0x39;    
        s->i[1] = 0x38;
        printf("%x\n", a.k);
    }
    输出结果是( D )
    A) 3839        B) 3938            C) 380039         D) 不可预知

//原因是a没有被初始化,数据不确定

#include <stdio.h>
    void main()
    { 
        union
        { 
            int     k;
            char    i[2];
        }*s,a;
        a.k = 0;
        s = &a;
        s->i[0] = 0x39;    
        s->i[1] = 0x38;
        printf("%x\n", a.k);
    }
    输出结果是( A )
    A) 3839        B) 3938            C) 380039         D) 不可预知

这个题目一方面考了x86大小端知识,另一方面考了字节序。

题目二:

以下程序在小端序的情况下输出的结果是_____
#pragma pack(4)/*四字节对齐*/
int main(int argc, char* argv[])
{
    unsigned char puc[4];
    struct tagPIM
    {                                                                                                  
        unsigned char ucPim1;
        unsigned char ucData0:1;
        unsigned char ucData1:2;
        unsigned char ucData2:3;
    }*pstPimData;

    pstPimData = (struct tagPIM *)puc;

    memset(puc, 0, 4);
    pstPimData->ucPim1 = 1;
    pstPimData->ucData0 = 2;
    pstPimData->ucData1 = 3;
    pstPimData->ucData2 = 4;

    printf("%02X %02X %02X %02X\n", puc[0], puc[1], puc[2], puc[3]);
    return 0;
}
#pragma pack()/*恢复缺省对齐方式*/

正确答案:01 26 00 00

这个题目还涉及到指针指向一片内存,这片内存被初始化后,指针指向内存也被初始化。

答案:01 26 00 00

2024年2月18日17:36:13更新

今天重新看了下,还是懵逼了好一会啊。不会租后还是搞出来了

最后经过代码赋值,puc数组的内容是[0000 0001] 、[0010 0110]

然后输出为16进制格式,一个十六进制有8位,包括前四位和后四位组成,所以结果是01 26 00 00


2023年8月2日10:39:41更新,关于字节序,之前找工作笔试的时候也遇到过这个题,就是怎么验证大小端,现在有点理解了,之前看的那个结构体验证大小端的也能理解一些了。后来又看到另外一种方法验证大小端,这边写一下。

#include <stdlib.h>
#include <stdio.h>

void funBigSmall()
{
    int num = 0x01;
    char dat = *(char*)&num;
    printf("%d\n",dat);
    if(dat)
    {
        printf("小端模式\n");
    }
    else
    {
        printf("大端模式\n");
    }

    getchar();
}

int main()
{
    funBigSmall();

    return 0;
}

//1
//小端模式

顺便把结构体形式的也写一下

#include <stdlib.h>
#include <stdio.h>

union UN
{
    char x;
    int y;
};

void funBigSmall()
{
    union UN un;
    un.y = 0x01;
    char dat = un.x;
    printf("%d\n",dat);
    if(dat)
    {
        printf("小端模式\n");
    }
    else
    {
        printf("大端模式\n");
    }
}

int main()
{
    funBigSmall();
    
    return 0;
}

 perfect!

六、#define

分为#define定义标志符和#define定义宏

1、#define 定义标志符

#define    name   stuff

例子1: 见代码

2、#define 定义宏

#define    name(parament-list)   stuff

#define   max(x,y)    ( (x) > (y) ? (x) : (y) )

例子2: 见代码

//#define max 100

#define NUM 100

#define DEBUG_PRINT printf("file:%s\tline:%d\t \
                          date:%s\ttime:%s\n", \
						  __FILE__, __LINE__, \
						  __DATE__, __TIME__)

#define MAX(x,y)  ( (x) > (y) ? (x) : (y) )

#define PRINTF(x)  printf("%d\n",x)

#define PRINTF_S(x)  printf("%s\n",x)

//#define PRINTF_WITH_CHARACTER(x)  printf(#x:"%d\n",x)  //ERROR
#define PRINTF_WITH_CHARACTER(x)  printf(#x":%d\n",x)

#define MAKE_UP_WORD(a,b)  a##b

#define SHOW_WORD(a)  (#a)

#define TO_STRING(str) #str
#define TO_CONNECT(arg1,arg2) arg1##arg2
#define TO_CONNECT_STRING(arg1,arg2) TO_STRING(arg1##arg2)

int main()
{
    //printf("max:%d", NUM);
    PRINTF(NUM);

    int a = 12, b = 16;

    //printf("max:%d", MAX(b,a));
    PRINTF(MAX(b,a));
    PRINTF_WITH_CHARACTER(b/a);

    PRINTF_S(SHOW_WORD(hello));

    PRINTF_S(TO_CONNECT_STRING(hello,world));


    printf("file:%s, line:%d", __FILE__, __LINE__);
}

//打印100
//打印16
//打印 b/a:2
//打印hello
//hello world

举个##创建新函数名的例子

#define creatfun(name1, name2)\
void name1##name2()\
{\
printf("%s called\n", __FUNCTION__);\
}

#define dat 12

creatfun(the,function);

int main()
{
    thefunction();
    return 0;
}

//打印 thefunction called

3. ilearning上的一个题目

#define N 10

#define G(a) #a

#define H(a) G(a)

int func2() {
    printf( "%s\n",G(N) );
    printf( "%s\n",H(G(N)) );
    printf( "%s\n",G(H(N)) );
    printf( "%d\n",N );
    printf( "%s\n",H(N) );
    
    return 0;
}

//打印
// "N"
// "G(N)"
// "H(N)"
// 10
// "10"

//正确打印
// N
// "N"
// H(N)
// 10
// 10

宏展开时:
如果宏定义以#开头,不展开参数,直接替换。
故g(f(1,2))--->#f(1,2)--->"f(1,2)";
如果宏定义不以#开头,展开参数,直接替换,由外层向里层,如果碰到的是#开头的宏,不继续往里层展开,往外层展开。
由外层向里层,如果碰到的是以非#开头的宏,继续往里层走,直至最里层,开始一层层往外层展开。

七、算法

1、时间复杂度

        算法的时间复杂度是指执行算法所需要的计算工作量,即度量算法执行的时间长短,它定量描述了该算法的运行时间。
  按数量级递增排列,常见的时间复杂度有:常数阶O(1),对数阶O(log2n),线性阶O(n),线性对数阶O(nlog2n),平方阶O(n^2),立方阶O(n^3),...。

八、操作符

1、逗号表达式

#include <stdlib.h>
#include <stdio.h>

int main()
{
    int x = 1, y = 1, z = 0;
    z = x++, y++, ++y;
    printf("%d,%d,%d\n",x,y,z);
    return 0;
}

//2,3,1
//逗号在整个表达式中优先级最低,先是执行z = x++,z = 0; 然后执行y++, ++y

2、操作符优先级

void func()
{
    int b = 7;
    float a = 2.5, c = 4.7;
    a = a + (int)(b / 3 * (int)(a + c) / 2) % 4;
    printf("%0.2f\n", a);
}

//打印5.50

//解释
//该题重点关注2处强制类型转换的优先级,C语言中强制类型转换的运算优先级要高于乘除法。

九、内存与指针

一、迷途指针

指针被free或delete后,指针所指向的内存区域被释放了,但是指针还是指向那片内存。而后,如果你没有重新赋值就试图再次使用该指针,引起的结果是不可预料的。这样的指针就称为迷途指针。

正确处理方法是释放了指针后,将指针置为NULL或0。

二、题目

题目一、

下面哪种变量定义不当,可能导致栈溢出?( D)  

A、静态全局变量

B、动态全局变量

C、静态局部变量

D、动态局部变量

解释:

静态全局变量全局变量(外部变量)的说明之前再加static 就构成了静态全局变量。static全局变量只初使化一次,防止在其他文件单元中被引用。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。
静态局部变量在局部变量前面加上static后,就定义了静态局部变量,静态局部变量属于静态存储方式,静态局部变量只会被初始化一次,下次使用依据上一次保存的值
动态局部变量个人理解:应该就是局部变量吧。可能包括堆内存局部变量和占内存局部变量。
2、题目二

以下哪个变量是分配在 BSS 段: 答案:s1,s2,s3; 

int s1;
int s2 = 0;
static int s3 = 0;
static int s4 = 4;

int main()
{
    
    int s5;
    return 0;
}

解释:

BSS 段:用来存放程序中 未初始化 的全局变量

数据段:存放程序中 已初始化 的 全局变量

因此 s1、s2、s3 都是在 bss 段中的,s4 存储在数据段,局部变量 s5 存储在栈中。

2023年11月20日18:49:55

做错!

十、文件操作

1、介绍scanf、gets、fgets用法及特点

int main()
{

    //第一个当然是最常用的scanf啊
    char str1[100] = {'\0'};
    while(scanf("%s",str1) != EOF)
    {
        printf("%s",str1);
    }

    //scanf缺点是遇到空格就不读取stdin了,如果str是有空格的字符串,就只能读一部分。而且也是不安全函数
    //但是实际效果是输入 hello world,输出helloworld
    //有个疑问,scanf怎么结束,发现可能是自己打印了,去掉打印试试,也不行
    //查了百度竟然是这种方法(笑):2.执行后,控制台输入数字后,先换行,后ctrl+z,再换行可以退出

    char str2[100] = {'\0'};
    while(gets(str2) != NULL)
    {
        printf("%s",str2);
    }

    //gets好像对输入数据不做检查,比如输入大于100,那缓冲区就溢出了,不安全函数,新标准已经不用了。gets_s是安全函数

    char str3[100] = {'\0'};
    while(fgets(str3, 100, stdin) != NULL)
    {
        printf("%s",str3);
    }

    
    char str3[100];
    fgets(str3, 7, stdin);
    printf("%s",str3);
    //输入this is my test string,打印this i;fgets自动填充\0,6个字符加\0共7个。用while全打印,不知道原因

    //fgets对长度做检查,比gets好

    return 0;
}

//scanf返回类型EOF

//fgets会获取换行符

十一、数据类型

1、题目一

#include <stdio.h>						
int main()			
{			
	char	c;	
	unsigned char	uc;	
	unsigned short	us;				
	c  = 128;		
	uc = 128;					
	us = c + uc;		
	printf("0x%x ",us);					
	us = c + (short)uc;		
	printf("0x%x ",us);			
	us = (unsigned char)c + uc;
	printf("0x%x ",us);			
	us = c + (char)uc;
	printf("0x%x ",us);			
	return 0;	
}		

上述代码执行后输出结果是什么? 下面选项正确的是()

A、 0x0 0x0 0x100 0xff00;

B、 0x0 0x100 0x100 0xff0;    

C、 0x0 0x100 0x100 0x0;

D、 0x0 0x0 0x100 0x0

答案:A

2023年11月20日19:18:00:这个题目是一点都不会!

首先有个前提:

整型提升,如果是无符号数,高位补0。如果是有符号数,高位补1。

然后突然想到什么时候需要整型提升呢,查了一下百度。解释如下:


隐式整型转换

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

整型提升的意义

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度,一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

        通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。


了解了以上的原理之后,现在写一下这题的解:

一下都是提升为short int,实际上应该是提升为int

①us = c + uc;

c:          1111   1111   1000 0000

uc:        0000  0000   1000 0000

us:     1 0000 0000   0000 0000  (0x0)   【us是short类型,保留两个字节,以下类似】

②us = c + (short)uc;

c:          1111   1111    1000 0000

uc:        0000   0000  1000 0000

us:     1 0000 0000   0000 0000  (0x0)

③us = (unsigned char)c + uc;

c:          0000   0000    1000 0000

uc:        0000   0000   1000 0000

us:      0000 0001   0000 0000  (0x0100)

④us = c + (char)uc;

c:          1111   1111    1000 0000

uc:        1111   1111   1000 0000

us:      1  1111   1111   0000 0000  (0xff00)   


2024年1月19日14:41:59

今天看c和指针,看字符串这一章节的时候,提到strlen返回值是size_t,是无符号整型。如果指向下面的代码则会出错。

if(strlen(str1) - strlen(str2) >= 0)则永远为真。

这边涉及到两个点:一、size_t是无符号整型,之前一直迷迷糊糊。二、整型提升。

下面主要说的是整型提升。

自己写了下面的验证代码

int main(void)
{
    int a = 3;
    unsigned b = 7;
    if(a - b < 0)
    {
        printf("a < b");
    }
    else
    {
        printf("error");
    }
}

//输出结果是error。因为无符号和有符号运算,有符号整型提升为无符号,返回值为无符号类型。
//if判断结果永远为真


int main(void)
{
    int a = 3;
    unsigned b = 7;
    int c = a - b;
    printf("c:%d",c);
    if(c < 0)
    {
        printf("a < b");
    }
    else
    {
        printf("error");
    }
}

//输出结果-4
//a < b

 十二、操作系统

1、下列属于进程间通信方式且数据拷贝次数最少的是(单选题)C

A、全局变量        B、消息队列        C、共享内存        D、管道

解释:

进程间通信的方式有:管道(匿名管道、命名管道)、信号、消息队列、共享内存、信号。

消息队列和管道采用存储-转发方式,即数据先从发送方缓存区拷贝到内核开辟的缓存区中,然后再从内核缓存区拷贝到接收方缓存区,至少有两次拷贝过程。

Copy From User 用户态->内核态

Copy To User 内核态->用户态

共享内存的方案,两个运行中的进程共享数据,可有效减少数据拷贝的次数:

2、在linux系统中,以下说法正确的是(单选题)

A、malloc/free是线程安全函数

B、rand,srand是信号安全函数

C、mbrtoc16, c16rtomb, mbrtoc32, c32rtomb是信号安全函数

解释

malloc/free中使用了锁,可以保证并发安全,所以属于线程安全函数,但不属于信号安全函数(在信号处理函数中使用可能会引起死锁);其他两个选项中的函数都使用了全局变量,不属于信号安全函数。

3、自旋锁持锁期间不允许被中断打断(判断题)错误。

解释

自旋锁基本实现中不会关中断,持锁期间可以被中断打断;若在线程上下文与中断函数中都使用了相同接口,而该接口中使用了自旋锁,那么需要先保存中断状态,禁止本地中断,再获取自旋锁,即使用spin_lock_irqsave()接口

十三、const

1、题目

对于以下C++程序代码输出是____。答案D。

#include <iostream> 

using namespace std; 

int main(void) 

{ 

    const int a = 10; 

    int * p = (int *)(&a); 

    *p = 20; 

    cout<<"a = "<<a<<", *p = "<<*p<<endl; 

    return 0; 

} 

A、编译阶段报错运行阶段报错

B、a = 10, *p = 10

C、a = 20, *p = 20

D、a = 10, *p = 20

E、a = 20, *p = 10

解释:

这道题的答案和编译器有关,在vs下运行,结果是10,20,在gcc下运行结果是20,20。因为在vs下限制了这种操作。

对于const int a=10来说,其值是不能修改,但可以通过指针来修改。

题目来源:牛客网公司真题_免费模拟题库_企业面试|笔试真题

这个题目我选的A。认为指针指向了常量,修改值会报错,但是这个题目没有报错。

百度有以下两篇帖子讲的就是这个问题。 

【嵌入式开发学习】__为什么在C语言中能用指针修改const常量_c语言修改const常量_Rleco_的博客-CSDN博客

const变量通过指针修改 详解_const 修改-CSDN博客

2、题目2

牛客网公司真题_免费模拟题库_企业面试|笔试真题

有下面的代码

struct A1{
    virtual ~A1(){}
};
struct A2{
    virtual ~A2(){}
};
struct B1 : A1, A2{};
int main()
{
 B1 d;
 A1* pb1 = &d;
 A2* pb2 = dynamic_cast<A2*>(pb1);  //L1
 A2* pb22 = static_cast<A2*>(pb1);  //L2
 return 0;
}

A、L1语句编译失败,L2语句编译通过

B、L1语句编译通过,L2语句编译失败

C、L1,L2都编译失败

D、L1,L2都编译通过

解释:选B。我选错我A.


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值