八股总结
文章目录
- 八股总结
- C/C++
- 1.关键字 Static 的作用是什么?
- 2. Extern关键词作用?
- 3. “引用”与指针的区别是什么?
- 4. .h 头文件中的 ifndef/define/endif 的作用?
- 5. #include 与 #include “file.h”的区别?
- 6. 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?
- 7. 什么是平衡二叉树?
- 8. 堆栈溢出一般是由什么原因导致的?
- 9. 什么函数不能声明为虚函数?
- 10.虚函数的实现?
- 11. 队列和栈有什么区别?
- 12. 不能做 switch()的参数类型?
- 13. 局部变量能否和全局变量重名?
- 14. 如何引用一个已经定义过的全局变量?
- 15. 全局变量可不可以定义在可被多个.C 文件包含的头文件中?为什么?
- 16. C语言内存分布?
- 17. 堆栈区别?
- 18. new和malloc区别?
- 19. malloc,calloc,realloc?
- 20. malloc,kmalloc,vmalloc
- 21. 编译是什么?编译过程?
- 22. 动态链接和静态链接?
- 23. const用法以及作用?
- 24. const和define区别
- 25. CPU是大端序还是小端序
- 26. 关键字 volatile 有什么含义?
- 27. 结构体和联合体的区别?
- 28. 指针数组和数组指针?
- 29. 指针操作
- 30. 数组传参
- 31. 如何判断一段程序是由 C 编译程序还是由 C++编译程序编译的?
- 32. 论述含参数的宏与函数的优缺点
- 33. 用两个栈实现一个队列的功能?要求给出算法和思路
- Reference
C/C++
1.关键字 Static 的作用是什么?
- 在函数体内声明一个变量为静态变量,其值在在函数之间调用过程中维持不变;
- 在函数体外声明一个变量为静态全局变量,此变量仅能够在本文件内被使用,其他文件无法访问。
- 使用static定义静态函数,只能被本文件内的其他函数调用,不能被其他文件之内的函数调用。
- 在类里面定义静态数据成员,表明所有对象的此数据成员都是相同的,其归类所有,而不属于某个具体的对象,可以由类名或者对象名引用。
class Box
{
public:
static int height;
int width;
int length;
}
- 在类里面定义静态函数,静态函数主要用来操作静态对象,可以由类名或者对象名引用。(静态函数可以访问类的静态成员变量,但不能访问非静态成员变量,因为它没有隐式的this指针。静态函数可以通过类名直接调用,无需创建类的实例对象。静态函数不能被声明为const,因为它没有隐式的this指针来进行常量性约束。静态函数不会继承给派生类,每个类都有自己的静态函数副本。)
class Box
{
private:
static int height;
int width;
int length;
public:
static int volume();
}
2. Extern关键词作用?
- 扩展全局变量的作用域,可以使用extern提前声明,而在之后再定义;
#include<iostream>
using namespace std:
int main()
{
extern a,b;
cout<<a+b<<endl;
return 0;
}
int a=15,b=20;
- 多文件情况下,在此文件使用extern表明此变量在其他文件已有定义。如下,变量a在1中定义,在2中使用。
//file1.cpp
#include<iostream>
using namespace std;
//在file1.cpp文件中定义非静态外部变量a
int a=3;
int main()
{
.
}
//file2.cpp
#include<iostream>
using namespace std;
//在file2.cpp中声明file1.cpp中定义的非静态外部变量a
extern a;
int main()
{
cout<<a<<endl;
}
- 使用extern表面此函数已在外部其他文件有定义,可以直接使用。如下,函数max在文件1中定义,在文件2中使用。
//file1.cpp
#include<iostream>
using namespace std;
//在file1.cpp文件中定义了非静态外部函数max()
int max(int a,int b)
{
int z;
z=x>y?x:y;
return z;
}
//file2.cpp
#include<iostream>
using namespace std;
int main()
{ //声明函数max()为在其它文件中定义的外部函数
extern int max(int,int);
int a,b;
cin>>a>>b;
//调用max()函数
cout<<max(a,b)<<endl;
return 0;
}
3. “引用”与指针的区别是什么?
–指针:指针的本质是一块存储变量地址的区域(变量),因此不同类型的指针变量长度相同(32位4字节,64位八字节),而变量则和具体的数据类型相关。(bool 1字节,char 1字节,short 2字节,int 4字节,float 4字节,double 8字节,long 4或8字节,long long 8字节,enum 4字节)
–引用:引用是对已有变量起的别名,编译器并不分配额外的空间给引用,它与被引用变量共享同一块内存。
区别:
- 引用必须赋值初始化,而指针不需要
- 引用初始化之后不能修改,而指针可以在生存期间指向任一同类型的数据
- 存在空指针nullptr,但没有空引用
- 使用sizeof指针的长度是固定的,而引用则取决于其数据类型
- 引用由编译器自动解引用,指针需要*操作符解引用
- 存在多级指针,但不存在多级引用
- 引用使用起来比指针更加安全。
4. .h 头文件中的 ifndef/define/endif 的作用?
防止该头文件被重复引用
5. #include 与 #include “file.h”的区别?
答:前者是从 Standard Library 的路径寻找和引用file.h,而后者是从当前工
作路径搜寻并引用 file.h。
6. 全局变量和局部变量在内存中是否有区别?如果有,是什么区别?
答 :全局变量储存在静态数据区,局部变量在栈中。
7. 什么是平衡二叉树?
答 :左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于 1。
8. 堆栈溢出一般是由什么原因导致的?
- 没有回收垃圾资源
- 层次太深的递归调用
9. 什么函数不能声明为虚函数?
- 静态函数:静态函数是属于类的,并不属于某个具体对象。静态函数没有this指针,而虚函数需要通过this指针找到虚函数表
- 内联函数:内联函数是静态联编,虚函数是动态联编,两者相互冲突。
- 构造函数:构造函数创建对象时,虚函数表还未形成。
- 友元函数和普通函数:都不属于类的成员函数
10.虚函数的实现?
多态是 C++ 三大特性之一,多态离不开虚函数的定义,而它的实现机制更是离不开虚函数表。类的对象是通过虚表指针访问虚函数表的,而虚表指针属于 C++ 对象内存布局中的一部分。虚表指针(__vfptr,类型 const void**)
对于不同的继承场景,类的虚函数表的形式是不一样,接下来分四种情况介绍。
1. 单继承且本身不存在虚函数的派生类内存布局
class Base1 {
public:
int base1_1, base1_2;
virtual void base1_fun1() {} // 定义虚函数
virtual void base1_fun2() {}
};
class Derive1 : public Base1 { // Derive1 中不存在虚函数
public:
int derive1_1, derive1_2;
};
2. 单继承且存在基类虚函数覆盖的派生类内存布局
class Base1 {
public:
int base1_1, base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Derive1 : public Base1 {
public:
int derive1_1, derive1_2;
virtual void base1_fun1() {} // 派生类函数覆盖基类中同名函数
};
3. 单继承且派生类存在属于自己的虚函数
class Base1 {
public:
int base1_1, base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Derive1 : public Base1 {
public:
int derive1_1, derive1_2;
virtual void derive1_fun1() {} // 派生类存在属于自己的虚函数
};
4. 多继承且存在虚函数覆盖同时又存在自身定义的虚函数的派生类对象布局
class Base1 {
public:
int base1_1, base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Base2 {
public:
int base2_1, base2_2;
virtual void base2_fun1() {}
virtual void base2_fun2() {}
};
class Derive1 : public Base1, public Base2 { // Derive 1 分别从 Base 1 和 Base2 继承过来
public:
int derive1_1, derive1_2;
virtual void base1_fun1() {}
virtual void base2_fun2() {}
virtual void derive1_fun1() {}
virtual void derive1_fun2() {}
};
11. 队列和栈有什么区别?
答:队列先进先出,栈后进先出
12. 不能做 switch()的参数类型?
C/C++中:
支持类型:byte,char,short,int,long,bool,枚举类型。
不支持的类型:float,double,string
13. 局部变量能否和全局变量重名?
能,局部会屏蔽全局。要用全局变量,需要使用”::”
#include <iostream>
int x = 5; // 全局变量
void myFunction() {
int x = 10; // 局部变量,与全局变量重名
std::cout << "局部变量 x = " << x << std::endl; // 输出局部变量 x
std::cout << "全局变量 x = " << ::x << std::endl; // 使用作用域解析运算符访问全局变量 x
}
int main() {
myFunction();
return 0;
}
14. 如何引用一个已经定义过的全局变量?
答 :可以用引用头文件的方式,也可以用 extern 关键字,如果用引用头文件方
式来引用某个在头文件中声明的全局变量,假定你将那个变量写错了,那么在编
译期间会报错,如果你用 extern 方式引用时,假定你犯了同样的错误,那么在
编译期间不会报错,而在连接期间报错。
15. 全局变量可不可以定义在可被多个.C 文件包含的头文件中?为什么?
答 : 可以,在不同的 C 文件中以 static 形式来声明同名全局变量。可以在不同的 C 文件中声明同名的全局变量,前提是其中只能有一个 C 文件中对此变量赋初值,此时连接不会出错。
16. C语言内存分布?
栈区:由编译器自动分配释放,存放函数的参数值,局部变量的值等
堆区:一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收
全局/静态存储区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。(.data存储初始化后的,.bss存储未初始化的)
常量存储区:常量字符串,数字常量,const修饰的常量变量,枚举常量
代码区:二进制代码
静态内存分配:编译时分配。包括:全局、静态全局、静态局部三种变量。
动态内存分配:运行时分配。包括:栈(stack): 局部变量。堆(heap): c语言中用到的变量被动态的分配在内存中。(malloc或calloc、realloc、free函数)
举例:
//main.cpp
int a=0; //全局初始化区
char *p1; //全局未初始化区
main()
{
int b;栈
char s[]=”abc”; //栈
char *p2; //栈
char *p3=”123456″; //123456\0 在常量区,p3 在栈上。
static int c=0; //全局(静态)初始化区
p1 = (char*)malloc(10);
p2 = (char*)malloc(20); //分配得来得 10 和 20 字节的区域就在堆区。
strcpy(p1,”123456″); //123456\0 放在常量区,编译器可能会将它与 p3 所向”123456″优化成一个地方。
}
17. 堆栈区别?
堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别:
1)管理方式不同。
栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;
2)空间大小不同。
每个进程拥有的栈的大小要远远小于堆的大小。
3)生长方向不同。
堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
4)分配方式不同。
堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。
5)分配效率不同。
栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。
6)存放内容不同。
栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容(运行上下文)等。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。
18. new和malloc区别?
- 申请位置:new在自由存储区(free store),malloc在堆上
- 语言以及类型安全:new是c++运算符,malloc是c语言库函数。new是类型安全的,new得到的内存是申请数据数据类型的指针,而malloc则需要进行强制转换。
- 内存分配:new动态分配内存,根据对象类型不同,分配不同的内存。malloc根据申请的字节数分配内存。new内存分配失败时返回bad_alloc异常,malloc失败返回NULL
- 构造和析构:new可以同时进行内存分配和自动调用构造函数,malloc只分配。
内存释放,new使用delete且自动析构,malloc使用free
19. malloc,calloc,realloc?
1、malloc()
头文件:#include <stdlib.h>
函数原型:void* malloc (size_t size);
size 为需要分配的内存空间的大小,以字节(Byte)计。malloc() 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。
2、calloc()
头文件:#include <stdlib.h>
calloc() 函数用来动态地分配内存空间并初始化为 0,其原型为:
void* calloc (size_t num, size_t size);
calloc() 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。
3、realloc()
void * realloc(void * mem_ address, size_ t newsize) ;
功能:
1)为已有内存的变量重新分配新的内存大小(可大、可小) ;
2)先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_ address返回;
3)如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address 所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
4)如果重新分配成功则返回指向被分配内存的指针;
5)如果分配不成功,返回NULL。
6)当内存不再使用时,应使用free ()函数将内存块释放
20. malloc,kmalloc,vmalloc
- 内核和用户:malloc是c库函数,是用户在用户空间分配内存的函数。kmalloc和vmalloc都是用于在内核空间分配内存的函数。
- 大小和连续:kmalloc分配的内存在物理地址是连续的,vmalloc申请的内存在虚拟内存空间是连续的,但在物理空间并不一定连续。kmalloc申请大小有限制(128kb),vmalloc可以用于申请较大内存。(内存只有在要被DMA访问的时候才需要物理上连续)
- vmalloc比kmalloc要慢,kmalloc开销小。
- kmalloc是原子过程,vmalloc不是,可以阻塞。
21. 编译是什么?编译过程?
编译是将高级编程语言转成计算机可执行语言的过程。
过程分为预处理,编译,汇编,链接
预处理包含头文件的展开,删除注释,添加行号和文件标识,处理预处理指令#id #ifdef等,预处理得到.i文件
编译:文件做词法分析,语法分析,语义分析以及优化得到汇编.s文件
汇编:将.s文件翻译为及其语言,生成.o文件(可重定位目标文件)
链接:链接到其他依赖的文件
22. 动态链接和静态链接?
静态链接:链接器在链接阶段将各种库文件和相关文件集成到可执行文件之中。在win下是.lib,在linux是.a文件
动态链接:链接过程发生在可执行文件装载或者运行的时候。在win下是.dll,在linux是.so文件
静态链接和动态链接的区别:
1 链接时机不同,静态链接发生在编译期间,将所有需要的库文件和可执行文件合并。动态链接发生在运行时,将所需要的代码库和可执行文件关联。
2 内存占用:静态链接文件包含了所有的库函数代码实现,因此比较大。动态链接将可执行文件和库文件分离,且多个程序可以共享一个库,因此内存占用少
3 静态链接每次程序更新都需要重新编译和链接,动态链接只需要更新库文件即可。
4 静态无需加在外部库,运行速度快
23. const用法以及作用?
- 作用于局部变量:初始化之后不能再改变
const int n=5;
int const n=5;
- 作用于指针
常量指针: 指针是常量,内容可以改,地址不能改
int x = 5;
int y = 10;
int* const ptr = &x; // 声明常量指针,指向整型变量
*ptr = 7; // 合法,通过常量指针修改变量的值
ptr = &y; // 非法,无法改变常量指针的指向
指针常量: 指针指向常量(pointer to constant),地址可以改,内容不能改。
int x = 5;
int y = 10;
const int* ptr; // 声明指针常量,指向整型常量
ptr = &x; // 合法,ptr 指向 x
*ptr = 7; // 非法,无法通过指针常量修改变量的值
ptr = &y; // 合法,可以改变指针的指向
- 修饰函数参数以及返回值
保护使其免受修改 - 类内成员和成员函数:
当函数的参数被声明为 const 类型时,表示该参数是只读的,即在函数内部不能修改该参数的值。这种使用 const 的参数常常用于传递不需要修改的数据,以增加程序的安全性和可靠性。当函数声明中的函数名前面加上 const 关键字时,表示该函数是一个常量成员函数。常量成员函数承诺不会修改任何类的成员变量。这样的函数可以在常量对象上调用,但不能修改类的成员状态(除了声明为 mutable 的成员变量)。
const 修饰成员变量:
- const 修饰的成员变量是常量,其值在对象创建后就不能被修改。
- const 成员变量必须在对象的构造函数初始化列表中进行初始化,而不能在构造函数的函数体内赋值。
- 一旦 const 成员变量被初始化,它的值将在整个对象生命周期中保持不变。
class MyClass {
public:
const int a; // 声明 const 成员变量
MyClass(int value) : a(value) { // 初始化列表中初始化 const 成员变量
}
};
const 修饰成员函数:
- const 修饰的成员函数被称为常量成员函数(const member function),表示该函数不会修改类的数据成员(非 mutable 的)。
- 在常量成员函数中,不能修改类的非 mutable 数据成员,也不能调用非常量成员函数(除非使用 mutable 关键字修饰)。
- 常量对象(const 对象)只能调用常量成员函数。
class MyClass {
private:
int x;
public:
void setX(int value) {
x = value;
}
int getX() const { // 声明常量成员函数
// x = 10; // 非法,在常量成员函数中不能修改非 mutable 的数据成员
return x;
}
};
静态成员变量
- 静态常量成员变量是类的所有对象共享的,不会随着对象的创建而分配内存,而是与类本身相关联。
- 静态常量成员变量在编译时进行初始化,并且在整个程序运行期间保持不变。
- 静态常量成员变量必须在类定义外部进行初始化,且只能初始化一次。
class MyClass {
public:
static const int MAX_VALUE = 100; // 声明并初始化静态常量成员变量
static void printMaxValue() {
std::cout << "The maximum value is: " << MAX_VALUE << std::endl;
}
};
// 在类定义外部初始化静态常量成员变量
const int MyClass::MAX_VALUE;
int main() {
int value = MyClass::MAX_VALUE; // 访问静态常量成员变量
MyClass::printMaxValue(); // 调用静态成员函数
return 0;
}
24. const和define区别
- const有数据类型,可以进行类型安全检查,define只是做字符串替换
- const有作用域限制,define没有
25. CPU是大端序还是小端序
ARM,X86是小端模式,MIPS和网络字节序一样是大端序。
大端序:高字节地址存在内存的地址高位,低字节地址存在内存低位
如何判断?
/* 联合(union)方式判断法 */
typedef union {
int i;
char c;
}my_union;
int check(void)
{
my_union u;
u.i = 1; //整形赋值0x 00 00 00 01,小端的话char型取01,大端取00
return (u.i == u.c); // 0:大端 1:小端
}
26. 关键字 volatile 有什么含义?
缓存一致性问题: 处理器的处理速度与内存的读取速度之间的差异很大,因此在计算机系统中引入缓存机制(Cache),对于多处理器系统而言,每个处理有不同的cache,但是共享同一块内存,因此会导致不同cache会对同一变量做不同的操作,内存的数据将变得混乱。由此,引入缓存一致性协议(MSI,MESI)保证一致性。
指令重排序问题: 处理器为了提升执行速度,改变程序中指令的执行顺序,但是保证最终结果与不发生重排序时一致。因此,但一个计算任务依赖于另一个任务的中间结果就会发生错误。
原子性: 一个指令要不不执行,要不全部执行。
可见性: 一个线程修改了共享变量的值,其他线程能够立刻感知到。
有序性: 线程中的操作按照顺序执行。
volatile的作用: 避免编译器优化:禁止指令重排序,保证缓存一致性(无法保证原子操作)
应用场景:
- 多线程或者并发环境中,多个线程同时操作一个变量
- 外设以及中断处理:在外设和中断处理过程中,读取寄存器值,处理中断标志,不应该被优化。
其它问题:
- 一个参数既可以是 const 还可以是 volatile 吗?解释为什么?
答:可以,const是指程序不应该修改此变量,volatile表示此变量是易变的。一个例子是状态寄存器,不应该被修改且易改变。 - 一个指针可以是 volatile 吗?解释为什么?
答:可以。比如一个中断服务函数修改一个指向buffer的指针的时候。 int square(volatile int* ptr) { return *ptr * *ptr; }
这段代码的错误?
编译器将产生类似的代码:
int square(volatile int *ptr) { int a,b; a = *ptr; b = *ptr; return a * b; }
此时a,b可能不一样,正确代码如下:
long square(volatile int *ptr) { int a; a = *ptr; return a * a; }
27. 结构体和联合体的区别?
- 结构体内的各变量内存空间独立,而结构体共享一块内存。因此,结构体占用内存空间大,联合体小。
- 结构体每次可以访问任意的成员,但是结构体一次只能访问一个成员,对一个成员的修改会引起其他成员的变化。
28. 指针数组和数组指针?
- 指针数组(Array of Pointers):
- 定义:指针数组是一个数组,其中每个元素都是指针类型。
- 内存结构:指针数组创建了一个连续的内存块,每个元素是一个指针,可以指向不同的内存地址。
- 访问方式:通过数组索引访问指针数组中的元素,然后可以使用解引用操作符 * 来访问指针指向的值。
int* arr[5]; // 声明了一个包含5个指针元素的指针数组
int a = 1, b = 2, c = 3;
arr[0] = &a; // 将指针指向变量 a 的地址
arr[1] = &b;
arr[2] = &c;
- 数组指针(Pointer to Array)
- 定义:数组指针是一个指针,指向一个数组。
- 内存结构:数组指针本身只存储数组的起始地址,不占用连续的内存空间。通过指针加偏移量来访问数组中的元素。
- 访问方式:通过解引用操作符 * 加上偏移量来访问数组指针指向的数组中的元素。
int (*ptr)[5]; // 声明了一个指向包含5个整型元素的数组的指针
int arr[5] = {1, 2, 3, 4, 5};
ptr = &arr; // 将指针指向数组 arr 的地址
(*ptr)[2] = 10; // 通过指针访问数组中的元素并修改其值
29. 指针操作
数组的首地址和元素首地址:数组名是元素首地址,但在sizeof和&时bu不是这样。
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
运行结果如下:两者地址相同,但是arr表示首元素地址,&arr表示数组地址
偏移之后:
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr);
printf("arr+1 = %p", arr + 1);
printf("&arr+1 = %p", &arr + 1);
return 0;
}
运行结果如下:可以发现,首元素地址偏移一个int类型,数组地址偏移一个数组长度
使用sizeof(arr),答案是40,使用sizeof(&arr),答案是8。
30. 数组传参
数组传参的途径有两种:
1. 值传递
通常,对一般变量的值传递,函数会拷贝一个与实参的值相同的临时变量来使用,因此,在函数内部改变该变量并不会真正改变原变量的实际值。 但是,对数组来说,通过值传递的函数,也能改变原数组的实际值。
值传递方式:
// 值传递
void test(int arr[]);
原因:
数组传参时发生了“降维”,实际上传入的是该数组首元素的指针
->避免拷贝完整数组带来的低效率
参数传参仍然为值传递,只不过拷贝的是数组首元素地址的临时指针
因此,对于这种数组通过“值传递”传参的函数,函数内部对数组元素的改变是真实存在的
另外:参数中的[]内可以省略数组具体大小的原因是,实际传参为数组首元素的指针,与数组实际大小并无关,因此此处[]内写了跟没写没有区别,只要不要写<=0的数即可。
而且,数组传参本来传的就是数组名,即数组首元素的地址,因此该函数形参也应为指针类型。
#include<iostream>
using namespace std;
void test(int arr[])
{
arr[0] = 1;
cout << &arr << endl; // 值传递后拷贝的临时指针
}
int main()
{
int arr[5] = { 0 };
cout << &arr << endl; // 指向原数组首元素地址的指针的地址
cout << arr[0] << endl;
test(arr);
cout << arr[0] << endl;
system("pause");
return 0;
}
从结果来看:传入的是数组地址的拷贝。
2. 地址传递: 使用指针或者引用
#include<iostream>
using namespace std;
// 一维数组的传参
void test01(int arr[]) // 值传递,传入的是一个int 类型的数组
{
cout << "test01" << endl;
for (int i = 0; i < 10; i++)
{
arr[i] = i;
}
}
void test02(int* arr[]) // 值传递,传入的是一个int* 类型的数组
{
cout << "test02" << endl;
for (int i = 0; i < 10; i++)
{
cout << arr[i] << " "; // arr[i] 为指针类型,此时空指针均指向0,不可解引用
}
cout << endl << endl;
}
void test03(int *arr) // 地址传递,传入数组时,int* 指向int类型数组的第一个元素的地址
{
cout << "test03" << endl;
for (int i = 0; i < 10; i++)
{
arr[i] = i;
}
}
void test04(int* *arr) // 地址传递,参数为指向指针的指针
// 可传入指针数组,此时int** 为一个指向int*类型数组头元素的指针
{
cout << "test04" << endl;
for (int i = 0; i < 10; i++)
{
cout << arr[i] << " "; // arr[i] 为指针类型,此时空指针均指向0,不可解引用
}
cout << endl << endl;
}
// 二维数组的传参
void test11(int arr[][5]) // 值传递,数组第二维不能为空,必须用数字初始化!
{
cout << "test05" << endl;
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 5; j++)
{
arr[i][j] = i * 5 + j;
}
}
}
void test12(int (*arr)[5]) // 地址传递,参数为一个指向int 类型数组的指针;
// 若传入二维数组,则指针指向第一维数组的第一个元素(第一个第二维的数组)
// 因此[]为数组第二维,必须初始化
{
cout << "test06" << endl;
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 5; j++)
{
arr[i][j] = (10 - i) * 5 - j - 1;
}
}
}
// 打印数组的函数
void print_linear_arr(int arr[])
{
for (int i = 0; i < 10; i++)
{
cout << arr[i] << " ";
}
cout << endl << endl;
}
void print_two_dimensional_arr(int (*arr)[5])
{
for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 5; j++)
{
cout << arr[i][j] << "\t";
}
cout << endl;
}
cout << endl;
}
int main()
{
int arr1[10] = { 0 };
int* arr2[10] = { 0 }; // 指针数组 (数组元素初始化为空指针)
int arr3[10][5] = { 0 };
// 一维数组的传参
test01(arr1);
print_linear_arr(arr1);
test02(arr2);
test03(arr1); // 参数为值传递,传入的是一个int* 类型的数组!非地址传递
print_linear_arr(arr1);
test04(arr2);
// 二维数组的传参
test11(arr3);
print_two_dimensional_arr(arr3);
test12(arr3);
print_two_dimensional_arr(arr3);
system("pause");
return 0;
}
31. 如何判断一段程序是由 C 编译程序还是由 C++编译程序编译的?
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
32. 论述含参数的宏与函数的优缺点
带参宏 | 函数 | |
---|---|---|
处理时间 | 编译时 | 运行时 |
参数类型 | 没有参数类型问题 | 定义实参、形参类型 |
处理过程 | 不分配内存 | 分配内存 |
程序长度 | 变长 | 不变 |
33. 用两个栈实现一个队列的功能?要求给出算法和思路
入队:将元素送入栈A
出队:判断栈B是否为空,如果栈B为空,则将A压入B,然后弹出栈顶元素。不为空,直接弹出栈顶元素。
Reference
static和extern用法总结
引用与指针的区别
排序算法
排序算法2
什么函数不能声明为虚函数?
虚函数实现
大端序和小端序
volatile
指针操作
数组传参
感谢各位大佬!