C/C++ 里面的考点(持续更新)

C/C++ 里面的考点

通过自己在学习和笔试面试的路上,边学边总结一些在C与C++里的考点,将以记录的形式来持续更新
内容除自己总结外,大多也来源于网上的各大博主,在此感谢诸位致力于分享知识的大佬
最新更新日期:2022/9/27

更新内容:

四十一、结构体与集合的大小(位域篇)



一、引用与指针的区别点:

  1. 两者的定义和性质不同:
指针是一个变量,存储的是一个地址,指向内存的一个存储单元;引用是原变量的一个别名,跟原来的变量实质上是同一个东西。
  1. 指针可以有多级,引用只能是一级
int **p; // 合法
int &&a; // 不合法
  1. 指针可以在定义的时候不初始化,引用必须在定义的时候初始化
int *p; // 合法
int &r; // 不合法
int a = 996;
int &r = a; // 合法
  1. 指针可以指向NULL,引用不可以为NULL
  1. 指针初始化之后可以再改变,引用不可以
int a = 996;
int *p = &a; // 初始化, p 是 a 的地址
int &r = a; // 初始化, r 是 a 的引用

int b = 885;
p = &b; // 合法, p 更改为 b 的地址
r = b; // 不合法, r 不可以再变
有细心的大佬提出了以下疑问:
int main() {
    int a=1;
    int b=2;
    int d = 3;
    int & c = a;
    cout << c <<endl;
    c = b;
    cout << c << a <<endl;
    c = d;
    cout << c << a << b <<endl;
}

输出:
1
22
332
这段代码是可以过编译的,也是可以正常输出的,那为什么说引用初始化后是不能改变的?
语句本身是合法的,引用初始化后不能改变指的是一旦绑定了引用关系后就无法更改,c = d或者c = b都只是在为a赋值,本质上并不是在更改c的引用关系。
如果在c = d的前面加上&,一样也是会报错的。

  1. sizeof 的运算结果不同
  1. 自增运算意义不同:
p++之后指向a后面的内存,r++相当于a++。
指针和引用都可以作为函数参数,改变实参的值。

二、strlen与sizeof存在的差异:

  1. strlen 是函数,sizeof 是运算符。
sizeof是在汇编里面就存在的一个指令,可以直接返回要判断的变量所占的内存大小(字节数)
  1. strlen 测量的是字符的实际长度,以'\0' 结束。而sizeof 测量的是字符的分配大小(变量所占空间)。
char str[20] = "hello";
printf("strlen: %d\n", strlen(str));
printf("sizeof: %d\n", sizeof(str));
[root@localhost 0703]# ./hello
strlen: 5
sizeof: 20
  1. sizeof可以用类型做参数,还可以用函数做参数;strlen只能用**char ***(字符串)做参数,且必须是以'\0'结尾的。
  1. 数组做sizeof的参数不退化,传递给strlen就退化为指针了
  1. 大部分编译程序在编译的时候就把sizeof计算过了,是类型或是变量的长度,这就是sizeof(x)可以用来定义数组维数的原因。
而strlen的结果要在运行的时候才能计算出来,是用来计算字符串的长度的,不是类型占内存的大小。

三、纯虚类

只要含有一个就是纯虚类

四、数组指针与指针数组

下面到底哪个是数组指针,哪个是指针数组呢:
A)
int *p1[10];//先声明p1数组,后用int *修饰数组内容(指针)
B)
int (*p2)[10]; //先修饰指针p2,然后申明数组内容
每次上课问这个问题,总有弄不清楚的。这里需要明白一个符号之间的优先级问题。
“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。

五、二维数组

例如,对于数组 a[5][3],按行分段赋值应该写作:
int a[5][3]={ {80,75,92}, {61,65,71}, {59,63,70}, {85,87,90}, {76,77,85} };
按行连续赋值应该写作:
int a[5][3]={80, 75, 92, 61, 65, 71, 59, 63, 70, 85, 87, 90, 76, 77, 85};
这两种赋初值的结果是完全相同的。
【实例2】和“实例1”类似,依然求各科的平均分和总平均分,不过本例要求在初始化数组的时候直接给出成绩。
#include <stdio.h>
int main(){
    int i, j; //二维数组下标
    int sum = 0; //当前科目的总成绩
    int average; //总平均分
    int v[3]; //各科平均分
    int a[5][3] = {{80,75,92}, {61,65,71}, {59,63,70}, {85,87,90}, {76,77,85}};

    for(i=0; i<3; i++){
        for(j=0; j<5; j++){
            sum += a[j][i]; //计算当前科目的总成绩
        }
        v[i] = sum / 5; // 当前科目的平均分
        sum = 0;
     }

    average = (v[0] + v[1] + v[2]) / 3;
    printf("Math: %d\nC Languag: %d\nEnglish: %d\n", v[0], v[1], v[2]);
    printf("Total: %d\n", average);

    return 0;
}
运行结果:
Math: 72
C Languag: 73
English: 81
Total: 75
总结下来就是a[行数][列数],按每行每行的初始化
利用*(a+i)+j与&a[i][j]等价

六、?:

? :
条件表达式
如果条件为真 ? 则值为 X : 否则值为 Y

a = 10;
b = (a == 1) ? 20: 30;
printf( "b 的值是 %d\n", b );
//b 的值是 30

七、死循环判断

A由于i会不断+1,所以最后99的时候会余99+1等于100并退出
C由于K会不断自增,在超出int类型大小范围时溢出变为负数后退出

八、函数不传参

九、static的用法(定义和用途)(必考)

1)用static修饰局部变量:使其变为静态存储方式(静态数据区),那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中。
2)用static修饰全局变量:使其只在本文件内部有效,而其他文件不可连接或引用该变量。
3)用static修饰函数:对函数的连接方式产生影响,使得函数只在本文件内部有效,对其他文件是不可见的(这一点在大工程中很重要很重要,避免很多麻烦,很常见)。这样的函数又叫作静态函数。使用静态函数的好处是,不用担心与其他文件的同名函数产生干扰,另外也是对函数本身的一种保护机制。

十、const的用法(定义和用途)(必考)

const主要用来修饰变量、函数形参和类成员函数:
1)用const修饰常量:定义时就初始化,以后不能更改。
2)用const修饰形参:func(const int a){};该形参在函数里不能改变。
3)用const修饰类成员函数:该函数对成员变量只能进行只读操作,就是const类成员函数是不能修改成员变量的数值的。
被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。

十一、const常量和#define的区别(编译阶段、安全性、内存占用等)

用#define max 100 ; 定义的常量是没有类型的(不进行类型安全检查,可能会产生意想不到的错误),所给出的是一个立即数,编译器只是把所定义的常量值与所定义的常量的名字联系起来,define所定义的宏变量在预处理阶段的时候进行替换,在程序中使用到该常量的地方都要进行拷贝替换;
用const int max = 255 ; 定义的常量有类型(编译时会进行类型检查)名字,存放在内存的静态区域中,在编译时确定其值。在程序运行过程中const变量只有一个拷贝,而#define所定义的宏变量却有多个拷贝,所以宏定义在程序运行过程中所消耗的内存要比const变量的大得多。
总结宏定义的缺点:定义的常量没有类型,编译时不会进行类型的安全检查
在程序中用到的常量都会进行拷贝替换,相比const只拷贝一次会消耗大量内存

十二、new/delete与malloc/free的区别是什么?

  1. new、delete是C++中的操作符,而malloc和free是标准库函数。
  1. 对于非内部数据对象来说,只使用malloc是无法完成动态对象要求的,一般在创建对象时需要调用构造函数,对象消亡时,自动的调用析构函数。而malloc free是库函数而不是运算符,不在编译器控制范围之内,不能够自动调用构造函数和析构函数。而NEW在为对象申请分配内存空间时,可以自动调用构造函数,同时也可以完成对对象的初始化。同理,delete也可以自动调用析构函数。而mallloc只是做一件事,只是为变量分配了内存,同理,free也只是释放变量的内存。
  1. new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而 malloc需要我们计算申请内存的大小,并且在返回时强行转换为实际类型的指针。

十三、构造函数与析构函数

析构函数不返回任何值,没有函数类型,也没有函数参数,因此它不能被重载。 构造函数可能有多个(重载),但析构函数只能有一个,就像人来到人世间,可能出生的环境家庭不同(重载构造函数),但最终都会死亡(析构函数)。
子类的构造方法执行前总是要先执行父类的无参构造方法。构造函数与父类的其它成员(成员变量和成员方法)不同,它不能被子类继承。因此,在创建子类对象时,为了初始化从父类中继承来的成员变量,编译器需要调用其父类的构造函数。如果子类的构造函数没有显示地调用父类的构造函数,则默认调用父类的无参构造函数。可以归纳出C++中子类继承父类时构造函数的写法的规律:当父类有显式地声明了构造函数时,子类最低限度的实现父类中的一个;当父类没有声明构造函数时,子类可以不声明构造函数或者任意地书写构造函数。
析构函数调用的次序是:先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了
定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;
总的来说,先生老爸再生儿子,先杀儿子再杀老爸

十四、new和malloc

做嵌入式,对于内存是十分在意的,因为可用内存有限,所以嵌入式笔试面试题目,内存的题目高频。
1)malloc和free是c++/c语言的库函数,需要头文件支持stdlib.h;new和delete是C++的关键字,不需要头文件,需要编译器支持;
2)使用new操作符申请内存分配时,无需指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地支持所需内存的大小。
3)new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无需进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void*,需要通过强制类型转换将void*指针转换成我们需要的类型。
4)new内存分配失败时,会抛出bad_alloc异常。malloc分配内存失败时返回NULL。

十五、*p++ 自增p 还是p 所指向的变量?

*p++ 和*(p++) 等价,都是p自增;
要自增p 指向的值, 使用(*p)++, 或者++*p。

十六、C++纯虚函数,虚函数,虚函数的实现,什么是虚指针?

纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。
virtual void f()=0;//是一个接口,子类必须实现这个接口虚指针或虚函数指针是虚函数的实现细节。带有虚函数的每一个对象都有一个虚指针指向该类的虚函数表。虚函数 :虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态覆盖(Override)
纯虚函数和虚函数的区别是,纯虚函数子类必须实现。 纯虚函数的优点:(1)可以实现多态特性
(2)定义一个标准的接口,在派生类中必须予以重写以实现多态性。
抽象类 :包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
多态性可分为两类:静态多态和动态多态。函数重载和运算符重载实现的多态属于静态多态,动态多态性是通过虚函数实现的。
虚函数与构造函数,析构函数,成员函数的关系
为什么基类析构函数是虚函数?
编译器总是根据类型来调用类成员函数。但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源泄漏。
为什么构造函数不能为虚函数
虚函数采用一种虚调用的方法。需调用是一种可以在只有部分信息的情况下工作的机制。如果创建一个对象,则需要知道对象的准确类型,因此构造函数不能为虚函数。
如果虚函数是有效的,那为什么不把所有函数设为虚函数?
不行。因为每个虚函数的对象都要维护一个虚函数表,因此在使用虚函数的时候都会产生一定的系统开销,这是没有必要的。

十七、const char*, char const*, char *const的区别是什么?

把一个声明从右向左读,* 读成指向
char * const cp;//cp是常指针,指向char类型的数据
const char * cp;//cp是char类型的指针,指向const char
char const * p;//C++里面没有const*的运算符,所以const属于前面的类型。

十八、什么是多态?多态有什么作用?如何实现的?多态的缺点?

多态就是一个接口,多种方法。所以说,多态的目的则是为了实现接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。
C++的多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或重写。而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同。编译器会根据函数列表的不同,而生成一些不同名称的预处理函数,来实现同名函数的重载。但这并没有体现多态性。
多态与非多态的实质区别就是函数的地址是运行时确定还是编译时确定。如果函数的调用在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的。而如果函数调用的地址在运行时才确定,就是动态的。
最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数

十九、int i=(j=4,k=8,l=16,m=32);printf("%d", i);输出值是多少

逗号表达式会顺序执行,由于执行到最后一个得到32,所以i=32.

二十、论述含参数的宏与函数的优缺点

1.函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简单的字符替换。
2.函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。
3.对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
4.调用函数只可得到一个返回值,而用宏调用可以设法得到几个结果。(可以在宏调用的调用多个语句来改变多个变量的值)
5.使用宏次数多时,宏展开后源程序长,因为每展开一次都使程序增长,而函数调用不使源程序变长。
6.宏替换不占运行时间,只占编译时间;而函数调用则占运行时间(分配单元、保留现场、值传递、返回)。
一般来说,用宏来代表简短的表达式比较合适。

二十一、关于函数调用(递归时)形参自增的情况分析

void gift(int x, int y,int count)
{
    //int num = count;
    if((x+y)>=3 && x>0 && y>0){
    //num+=1;
    count++;
    gift(x-1,y-2,count);
    gift(x-2,y-1,count);
    /*gift(x-1,y-2,count++)XXXXX切记不要使用自增,不然的话进入函数调用的还是自增前的形参count,++直到函数语句执行完后才自增(没有意义)*/
    }
    else{
    if(count>res)res=count;
    //cout <<x<<" "<<y<<" "<<res<<endl;
    return ;
    }
}
切忌在调用函数的时候使用参数自增i++/gift(x-1,y-2,count++)/,i++为先传入原值后自增,自增只在函数语句结束后执行,没有意义。
使用++i要小心的是,在上述代码有多个递归调用语句执行的时候,上一个函数调用后count会执行自增的,到第二条语句收到的count就是上一个函数调用语句自增后传下来的count;
所以最保险就是先自增后传参。

二十二、C++循环赋值并回车终止

vector<int> weight;
int n,W;
cin >> W;
while(cin.get() != '\n'){
cin >> n;
weight.push_back(n);
}

二十三、C++的内存分布;堆与栈的区别

text(代码段): 用来存放程序执行代码,同时也可能会包含一些常量(如一些字符串常量等)。该段内存为静态分配,只读(某些架构可能允许修改)
data(数据段):用来存放程序中已经初始化的非零全局变量,静态分配。data又可分为读写(RW)区域和只读(RO)区域,RO段保存常量所以也被称为.constdata,RW段则是普通非常全局变量,静态变量就在其中
bss:存放程序中未初始化的和零值全局变量。静态分配,在程序开始时通常会被清零。
堆:在内存开辟另一块存储区域,般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收
栈:程序运行时由编译器自动分配,存放函数的参数值,局部变量的值等
堆和栈的区别:
  • 栈由系统自动分配和管理,堆由程序员手动分配和管理
  • 栈由系统分配,速度快,不会有内存碎片
  • 堆由程序员分配,速度较慢,可能由于操作不当产生内存碎片
  • 栈从高地址向低地址进行扩展,堆由低地址向高地址进行扩展
  • 程序局部变量是使用的栈空间,new/malloc 动态申请的内存是堆空间,函数调用时会进行形参和返回值的压栈出栈,也是用的栈空间
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意
思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有
的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将
提示overflow。因此,能从栈获得的空间较小。这样的好处是
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储
的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小
受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

二十四、栈由高地址向低地址拓展的优点

因为当栈由高地址拓展向低地址的时候,这样栈的起始位置就固定了下来,在动态调整栈的大小的时候就不用移动栈内数据
但是当栈由低地址拓展向高地址的时候,结尾地址是固定的,动态调整就需要挪动整个栈的数据

二十五、C++中"::"、"."和"->"的作用

::指的是对类成员函数的引用
.指的是对类类型对象成员的引用
->指的是对类类型指针指向的类的成员的引用

二十六、二维数组的初始化问题

int n,m;
cin >> n >> m;
int dp1[n][m];
int dp2[n][m]={0};
int dp3[100][100];
int dp4[100][100] = {0};
其中只有3、4的二维数组初始化时将全部元素置为0没有问题,因为这是利用常量静态分配初始化的
而1、2是利用变量动态分配,初始化为0会出错,要通过遍历或者memset函数来置0,即使对变量手动赋值也不可以
一维数组也是一样

二十七、C++派生类中与基类同名函数的调用问题

针对通过子类调用基类的同名函数,考虑以下两种情况:
  1. 同名不同参
直接用子类对象调用会报错,只有非成员函数或者同一类内的函数同名不同参才会发生对参重载
  1. 同名同参
这时候基类的函数会被子类的函数覆盖,直接用子类对象调用同名函数会默认调用子类的函数。
解决方法:
1、定义基类指针,让基类指针指向派生类对象,用指针调用基类函数。
2、显示调用基类函数,既在基类函数之前加上基类名和域说明符。

二十八、类的内存分布

类的普通成员变量按顺序分布占用内存,这里的类的内存对齐原则与结构体的内存对齐原则是一样的
C++中成员函数和非成员函数都是存放在代码区的,故类中一般成员函数、友元函数,内联函数还是静态成员函数都不计入类的内存空间
当类中出现虚函数的时候,虚函数表指针要在类内占用一个int *类型的指针,指向虚函数表
而虚函数表放在全局数据区(data)进行维护,虚函数放在了代码段
类的静态成员变量编译时被分配到静态/全局区,因此静态成员变量是属于类的,所有对象共用一份,不计入类的内存空间。

二十九、sizeof(struct stu arr[2])

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

struct stru{
char a;
int b;
};

union a{
char a;
int b;
};
int main()
{
stru arr[2];
a arr2[2];
cout << sizeof(arr) << endl;
cout << sizeof(arr2) << endl ;
cout << sizeof(arr[0]) << endl ;
cout << sizeof(arr2[0]) << endl ;
}

16
8
8
4
由于字节对齐,一个结构体对象的内存占用为4+4=8
由于字节对齐,一个集合体对象的内存占用为4
sizeof(数组名字)返回的是整个数组占的大小,所以返回8*24*2

三十、static修饰变量在.h头文件中的影响

static修饰的全局变量的作用范围会被限制在声明的源文件内。
以三个文件举个例子,
a.c
b.c
c.h
其中c.h被a.c,b.c引用, 而且c.h中定义了:
static int test = 0;

在编译的时候,a.c会和c.h一起编译, 然后b.c也会和c.h一起编译, 但是要注意, 编译器分别编译这两组文件的时候, 变量test会分别分配地址, 然后初始值也都为0;
这就意味着, 在a.c中如果调用了test, 其初始值为0;假设在a.c中test随后被修改为了2, 然后b.c中也调用test,这时test的初始值还是0, 而不是2!
如果只是普通的int定义变量,这个变量的值是会在所有包含这个头文件的源文件里面共享的

三十一、指针相减问题

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(){
   short *q;
   short *p;
    q = p+1;
    cout << q-p <<endl;
    cout << (char *)q-(char *)p <<endl;
    cout << (double *)q-(double *)p <<endl;
}
输出:
1
2
0


指针类型强转影响的是取值的长度,指向的地址不变。
同理:
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(){
   double *q;
   double *p;
    q = p+1;
    cout << q-p <<endl;
    cout << (short *)q-(short *)p <<endl;
}
输出:
1
4


三十二、不使用sizeof,求占用的字节数

#define mysizeof(value) (char *)(&value+1) - (char *)(&value)
/*
(char *)(&value)返回value的第一个字节
(char *)(&value+1)返回value的地址的下一个地址的第一个字节
他们之差为他们所占的字节数
(char *)强转保证所求的是地址的第一个字节,所取得值为一个字节
如果指向的是别的类型,在地址的起始位开始取得值为别的类型对应的字节
*/

三十三、volatile关键字的作用

编译器为了提高存取的效率,有时会把一个变量先放进寄存器缓存起来,以后再取就直接从寄存器取而不用去内存。
在单线程环境中这样是有益的,但是在多线程中是有风险的:当一个线程保存了一个变量被另外一个线程修改后,将无法在寄存器中再读取。
volatile修饰的变量不会被优化,而是提示处理器每次都从对应的内存里提取这个变量的值。
volatile一般修饰被多个线程共享的变量等

三十四、a是变量,(a++) += a是否合法

左值最重要的特点是可写(可寻址),如果一个左值是不可被修改的,则不合法
a++的实现原理是生成一个临时变量记录a自增之前的值,然后返回这个临时变量,语句执行完再自增
临时变量并不是可寻址的变量,所以不可作为左值,这个语句是不合法的
如果a是指针,a++=a本身也是不合法的
*a++ = *a是合法的


三十五、引用作为返回值

百度一面的时候面试官问了一个很重要的问题:
从编程规范角度上,函数出参(返回)是引用好还是指针好?
但是个人思考基于引用是直接访问的理由选择了引用。
在网上直接搜很难找到统一的答案,在《程序员面试笔试第三版》中遇到了类似的问题:
要注意的是不能返回局部变量的引用,引用类似new返回的临时变量也不可以。

三十六、sizeof(++i + ++i)?

int main(){
    int i = 3;
    int j;
    j = sizeof(++i + ++i);
    printf("%d,%d",i,j);
}
思考一下上述代码的输出是什么?
答案是3,4
sizeof在上文中提到,是属于运算符/操作符的,其返回结果在编译的时候就完成了,所以括号内的所有运算都没有意义。
所以i的值不会自增。
其次要注意,sizeof一个数据变量返回的并不是变量的值,而是数据类型的大小
所以sizeof(++i + ++i)实际测量的是sizeof(int),在常见的系统(32/64)下返回的是4

三十七、指针悬挂与野指针

在一场面试中,被问到指针悬挂认识不认识,反问和野指针有什么关系吗,面试官说与野指针很像但不是一个东西
指针悬挂指的是指向的内存空间被删除了。例如申请一个str1的类拷贝给str2。两个类拷贝后共享一个指针,但是两个类分别析构的时候会对一块内存空间进行两次析构,就会产生指针悬挂的问题
野指针指的是指针未被初始化,随机指向了一块未知空间,从而不可直接使用。

三十八、#define和#typedef的区别

(1)原理不同
#define是C语言中定义的语法,是预处理指令,在预处理时进行简单而机械的字符串替换,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。
typedef是关键字在编译时处理,有类型检查功能。它在自己的作用域内给一个已经存在的类型一个别名,但不能在一个函数定义里面使用typedef。用typedef定义数组、指针、结构等类型会带来很大的方便,不仅使程序书写简单,也使意义明确,增强可读性。
2)功能不同
typedef用来定义类型的别名,起到类型易于记忆的功能。另一个功能是定义机器无关的类型。如定义一个REAL的浮点类型,在目标机器上它可以获得最高的精度:typedef long double REAL, 在不支持long double的机器上,看起来是这样的,typedef double REAL,在不支持double的机器上,是这样的,typedef float REAL
#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。
(3)作用域不同
#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而typedef有自己的作用域。

三十九、指针传参

void get(char* p,int num){
    p = (char *)malloc(sizeof(char)*num);
}

int main(){
   char *str;
   get(str,100);
   strcpy(str,"hello");
   return 0;
}
上述代码可以正常运行吗?
答案是不行的。问题出在了get函数的指针传参上!
指针作为参数传递的时候,原理和值传递还是一样的,指针作为地址还是作为一个形参,但地址指向的变量就可以以实参的形式修改。所以上述代码中malloc赋予的空间地址只是赋给了指针形参,其作用域只在函数局部范围内。
如果想要成功接收malloc返回的指针,就需要将参数修改成二级指针,这样一级指针就成为实参了

四十、数组名自增

int *p;
int a[10];
p = a;
/*下列哪个不能表示a[1]?*/
p+1
a++
p++
a+1
答案是a++
因为数组名自增,增加的是一整个数组长度的大小

四十一、结构体与集合的大小(位域篇)


对类A:
第一句声明对齐方式为4个字节(32位)
偏移量为0,a1占用30位,由于下一个变量不是一个类型的,剩下2位空闲占用
偏移量为4个字节,满足对齐要求,a2占用2位,由于下一个变量不是一个类型的,剩下30位空闲占用
偏移量为8个字节,满足对齐要求,a3占用16位,a4数据类型相同且刚好占用剩余16位。
偏移量为12个字节,满足对齐要求,a5占用8位(一个字节),由于下一个变量不是一个类型的,剩下24位空闲占用
偏移量为16个字节,满足对齐要求,a6占用3位,后面没有存储单元,直接补齐空闲占用
最后总偏移量为20个字节,且满足int大小的倍数

对类A如果不声明对齐方式:
偏移量为0,a1占用30位,由于下一个变量不是一个类型的,剩下2位空闲占用
偏移量为4个字节,满足对齐要求,a2占用2位,由于下一个变量不是一个类型的,剩下6位空闲占用
偏移量为5个字节,不满足对齐要求,补齐3个字节,a3占用16位,a4数据类型相同且刚好占用剩余16位。
偏移量为12个字节,满足对齐要求,a5占用8位(一个字节)
偏移量为13个字节,不满足对齐要求,补齐3个字节,a6占用3位,后面没有存储单元,直接补齐空闲占用
最后总偏移量为20个字节,且满足int大小的倍数

对集合B:
最长为int类型的b1,占用16位,剩余空闲占用补齐,总长度为4个字节

#笔试##面试##C/C++#
全部评论
hi~同学,秋招遇“寒气”,牛客送温暖啦!23届秋招笔面经有奖征集中,参与就得牛客会员7天免费体验,最高赢300元京东卡!戳我去看>>>https://www.nowcoder.com/link/zhengjipinglun
点赞
送花
回复
分享
发布于 2022-09-06 09:15 北京
指针和引用那里,关于引用初始化以后不可改变,我去测试了一下发现是可以的。 测试代码: int main() { int a = 1; int b = 2; int &c = a; cout << c << endl; c = b; cout << c << endl;    return 0; } 输出: 1 2
点赞
送花
回复
分享
发布于 2022-09-15 19:55 广东
滴滴
校招火热招聘中
官网直投

相关推荐

17 84 评论
分享
牛客网
牛客企业服务