参考:C/C++ 面试题
作者:zhaouc
发布时间: 2015-02-15 15:51:00
网址:https://blog.csdn.net/zhaouc/article/details/43835667
参考:C/C++常见面试知识点总结附面试真题----20210529更新
作者:kuweicai
发布时间: 2018-09-19 22:47:57
网址:https://blog.csdn.net/kuweicai/article/details/82779648?utm_medium=distribute.pc_relevant.none-task-blog-OPENSEARCH-2.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-2.nonecase
以下内容部分整理自网络,部分为自己面试的真题。
C中,内存分为5个区:堆(malloc)、栈(如局部变量、函数参数)、程序代码区(存放二进制代码)、全局/静态存储区(全局变量、static变量)和常量存储区(常量)。此外,C++中有自由存储区(new)一说。
全局变量、static变量会初始化为缺省值,而堆和栈上的变量是随机的,不确定的。
总的来说,堆是C语言和操作系统的术语,是操作系统维护的一块动态分配内存;自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。他们并不是完全一样。
从技术上来说,堆(heap)是C语言和操作系统的术语。堆是操作系统所维护的一块特殊内存,它提供了动态分配的功能,当运行程序调用malloc()时就会从中分配,稍后调用free可把内存交还。而自由存储是C++中通过new和delete动态分配和释放对象的抽象概念,通过new来申请的内存区域可称为自由存储区。基本上,所有的C++编译器默认使用堆来实现自由存储,也即是缺省的全局运算符new和delete也许会按照malloc和free的方式来被实现,这时藉由new运算符分配的对象,说它在堆上也对,说它在自由存储区上也正确。
程序编译的过程中就是将用户的文本形式的源代码(c/c++)转化成计算机可以直接执行的机器代码的过程。主要经过四个过程:预处理、编译、汇编和链接。具体示例如下。
一个hello.c的c语言程序如下。
#include
int main()
{
printf("happy new year!\n");
return 0;
}
其编译过程如下:
负数比较容易,就是通过一个标志位和补码来表示。
对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用32bit,double数据占用64bit,我们在声明一个变量float f= 2.25f的时候,是如何分配内存的呢?如果胡乱分配,那世界岂不是乱套了么,其实不论是float还是double在存储方式上都是遵从IEEE的规范的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。更多可以参考浮点数表示。
无论是单精度还是双精度在存储中都分为三个部分:
int main(void)
{
...
d = fun(a, b, c);
cout<
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
调用fun()的过程大致如下:
不是很严谨的来说,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),右值指的则是只能出现在等号右边的变量(或表达式)。举例来说我们定义的变量 a 就是一个左值,而malloc返回的就是一个右值。或者左值就是在程序中能够寻值的东西,右值就是一个具体的真实的值或者对象,没法取到它的地址的东西(不完全准确),因此没法对右值进行赋值,但是右值并非是不可修改的,比如自己定义的class, 可以通过它的成员函数来修改右值。
归纳一下就是:
用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。
这里考察的是c 中的默认类型机制。
主要有三点:
class Screen {
public:
const char cha; //const成员变量
char get() const; //const成员函数
};
const Screen screen; //只读对象
不能。c中的const仅仅是从编译层来限定,不允许对const 变量进行赋值操作,在运行期是无效的,所以并非是真正的常量(比如通过指针对const变量是可以修改值的),但是c++中是有区别的,c++在编译时会把const常量加入符号表,以后(仍然在编译期)遇到这个变量会从符号表中查找,所以在C++中是不可能修改到const变量的。
补充:
下面我们通过代码来看看区别。
同样一段代码,在c编译器下,打印结果为*pa = 4, 4
在c++编译下打印的结果为 *pa = 4, 8
int main(void)
{
const int a = 8;
int *pa = (int *)&a;
*pa = 4;
printf("*pa = %d, a = %d", *pa, a);
return 0;
}
另外值得一说的是,由于c++中const常量的值在编译期就已经决定,下面的做法是OK的,但是c中是编译通不过的。
int main(void)
{
const int a = 8;
const int b = 2;
int array[a+b] = {0};
return 0;
}
int *p = new int(1);
特别的,在C++中,如下的代码,用new创建一个对象(new 会触发构造函数, delete会触发析构函数),但是malloc仅仅申请了一个空间,所以在C++中引入new和delete来支持面向对象。
#include
class Test
{
...
}
Test* pn = new Test;
Test* pm = (Test*)malloc(sizeof(Test));
C中是直接在变量或者表达式前面加上(小括号括起来的)目标类型来进行转换,一招走天下,操作简单,但是由于太过直接,缺少检查,因此容易发生编译检查不到错误,而人工检查又及其难以发现的情况;而C++中引入了下面四种转换:
class test{
int m_value;
...
public:
operator int() //类型转换函数
{
return m_value;
}
int toInt() //显示调用普通函数来实现类型转换
{
return m_value
}
};
int main()
{
...
test a(5);
int i = a;
...
return 0;
}
class example{
public:
static int m_int; //static成员变量
};
int example::m_int = 0; //没有static
cout<
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
class example{
private:
static int m_int_s; //static成员变量
int m_int;
static int getI() //静态成员函数在普通成员函数前加static即可
{
return m_int_s; //如果返回m_int则报错,但是可以return d.m_int是合法的
}
};
cout<
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同,假设某个函数原型为:
void foo(int x, int y);
该函数被C编译器编译后在库中的名字为 _foo, 而C++编译器则会产生像: _foo_int_int 之类的名字。为了解决此类名字匹配的问题,C++提供了C链接交换指定符号 extern “C”。
相同点:
它们的作用是防止头文件被重复包含。
不同点
答:理论上++i更快,实际与编译器优化有关,通常几乎无差别。
//i++实现代码为:
int operator++(int)
{
int temp = *this;
++*this;
return temp;
}//返回一个int型的对象本身
// ++i实现代码为:
int& operator++()
{
*this += 1;
return *this;
}//返回一个int型的对象引用
i++和++i的考点比较多,简单来说,就是i++返回的是i的值,而++i返回的是i+1的值。也就是++i是一个确定的值,是一个可修改的左值,如下使用:
cout << ++(++(++i)) << endl;
cout << ++ ++i << endl;
可以不停的嵌套++i。
这里有很多的经典笔试题,一起来观摩下:
int main()
{
int i = 1;
printf("%d,%d\n", ++i, ++i); //3,3
printf("%d,%d\n", ++i, i++); //5,3
printf("%d,%d\n", i++, i++); //6,5
printf("%d,%d\n", i++, ++i); //8,9
system("pause");
return 0;
}
首先是函数的参数入栈顺序从右向左入栈的,计算顺序也是从右往左计算的,不过都是计算完以后再进行的压栈操作:
对于第1个printf,首先执行++i,返回值是i,这时i的值是2,再次执行++i,返回值是i,得到i=3,将i压入栈中,此时i为3,也就是压入3,3;
对于第2个printf,首先执行i++,返回值是原来的i,也就是3,再执行++i,返回值是i,依次将3,5压入栈中得到输出结果
对于第3个printf,首先执行i++,返回值是5,再执行i++返回值是6,依次将5,6压入栈中得到输出结果
对于第4个printf,首先执行++i,返回i,此时i为8,再执行i++,返回值是8,此时i为9,依次将i,8也就是9,8压入栈中,得到输出结果。
上面的分析也是基于VS搞的,不过准确来说函数多个参数的计算顺序是未定义的(the order of evaluation of function arguments are undefined)。笔试题目的运行结果随不同的编译器而异。
(1)隐藏。 当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性,故使用static在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。
(2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量。
(3)static的第三个作用是默认初始化为0.其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0×00,某些时候这一特点可以减少程序员的工作量。
不要一听到const就说是常量,这样给考官一种在和一个外行交谈的感觉。应该说const修饰的内容不可改变就行了, 定义常量只是一种使用方式而已,还有const数据成员,const参数, const返回值, const成员函数等, 被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
C中是使用宏#define定义, C++使用更好的const来定义。
区别:
1)const是有数据类型的常量,而宏常量没有,编译器可以对前者进行静态类型安全检查,对后者仅是字符替换,没有类型安全检查,而且在字符替换时可能会产生意料不到的错误(边际效应)。
2)有些编译器可以对const常量进行调试, 不能对宏调试。
const无法代替宏作为卫哨来防止文件的重复包含。
引用是对象的别名, 操作引用就是操作这个对象, 必须在创建的同时有效得初始化(引用一个有效的对象, 不可为NULL), 初始化完毕就再也不可改变, 引用具有指针的效率, 又具有变量使用的方便性和直观性, 在语言层面上引用和对象的用法一样, 在二进制层面上引用一般都是通过指针来实现的, 只是编译器帮我们完成了转换。 之所以使用引用是为了用适当的工具做恰如其分的事, 体现了最小特权原则。
1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,如全局变量,static变量。
2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
3)从堆上分配(动态内存分配)程序在运行的时候用malloc或new申请任意多少的内存,程序员负责在何时用free或delete释放内存。动态内存的生存期自己决定,使用非常灵活。
malloc() 与 free() 是C语言的标准库函数, new/delete 是C++的运算符, 他们都可以用来申请和释放内存, malloc()和free()不在编译器控制权限之内, 不能把构造函数和析构函数的任务强加给他们。www.cdtarena.com
答:对于#include
C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为: void foo(int x, int y);该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。C++提供了C连接交换指定符号extern"C"来解决名字匹配问题。
多态性是面向对象程序设计语言继数据抽象和继承之后的第三个基本特征。它是在运行时出现的多态性通过派生类和虚函数实现。基类和派生类中使用同样的函数名, 完成不同的操作具体实现相隔离的另一类接口,即把" w h a t"从"h o w"分离开来。多态性提高了代码的组织性和可读性,虚函数则根据类型的不同来进行不同的隔离。
在绝大多数情况下, 程序的功能是在编译的时候就确定下来的, 我们称之为静态特性。 反之, 如果程序的功能是在运行时刻才能确定下来的, 则称之为动态特性。C++中, 虚函数,抽象基类, 动态绑定和多态构成了出色的动态特性。
封装来源于信息隐藏的设计理念, 是通过特性和行为的组合来创建新数据类型让接口与具体实现相隔离。C++中是通过类来实现的, 为了尽量避免某个模块的行为干扰同一系统中的其它模块,应该让模块仅仅公开必须让外界知道的接口。
RTTI事指运行时类型识别(Run-time type identification)在只有一个指向基类的指针或引用时确定一个对象的准确类型。
它是单个参数的构造函数,其参数是与它同属一类的对象的(常)引用;类定义中,如果未提供自己的拷贝构造函数,C++提供一个默认拷贝构造函数,该默认拷贝构造函数完成一个成员到一个成员的拷贝
浅拷贝是创建了一个对象用一个现成的对象初始化它的时候只是复制了成员(简单赋值)而没有拷贝分配给成员的资源(如给其指针变量成员分配了动态内存); 深拷贝是当一个对象创建时,如果分配了资源,就需要定义自己的拷贝构造函数,使之不但拷贝成员也拷贝分配给它的资源。
开发时间短, 效率高, 可靠性高。面向对象编程的编码具有高可重用性,可以在应用程序中大量采用成熟的类库(如STL),从而虽短了开发时间,软件易于维护和升级。
相同点:
如下代码中对引用取地址,其实是取的引用所对应的内存空间的地址。这个现象让人觉得引用好像并非一个实体。但是引用是占用内存空间的,而且其占用的内存和指针一样,因为引用的内部实现就是通过指针来完成的。
比如 Type& name; <===> Type* const name。
int main(void)
{
int a = 8;
const int &b = a;
int *p = &a;
*p = 0;
cout<
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
在C中三目运算符(? :)的结果仅仅可以作为右值,比如如下的做法在C编译器下是会报错的,但是C++中却是可以是通过的。这个进步就是通过引用来实现的,因为下面的三目运算符的返回结果是一个引用,然后对引用进行赋值是允许的。
int main(void)
{
int a = 8;
int b = 6;
(a>b ? a : b) = 88;
cout<
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
数组指针,是指向数组的指针,而指针数组则是指该数组的元素均为指针。
类型名 (*数组标识符)[数组长度]
类型名 *数组标识符[数组长度]
该部分主要摘自:c++ 学习笔记
左值引用就是我们通常所说的引用,如下所示。左值引用通常可以看作是变量的别名。
type-id & cast-expression
// demo
int a = 10
int &b = a
int &c = 10 // 错误,无所对一个立即数做引用
const int &d = 10 // 正确, 常引用引用常数量是ok的,其等价于 const int temp = 10; const int &d = temp
右值引用是 C++11 新增的特性,其形式如下所示。右值引用用来绑定到右值,绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期。
type-id && cast-expression
// demo
int &&var = 10; // ok
int a = 10
int &&b = a // 错误, a 为左值
int &&c = var // 错误,var 为左值
int &&d = move(a) // ok, 通过move得到左值的右值引用
在汇编层面右值引用做的事情和常引用是相同的,即产生临时量来存储常量。但是,唯一 一点的区别是,右值引用可以进行读写操作,而常引用只能进行读操作。
右值引用支持移动语义的实现,可以减少拷贝,提升程序的执行效率。
下面的代码时没有采用右值引用时的实现。
class Stack
{
public:
// 构造
Stack(int size = 1000)
:msize(size), mtop(0)
{
cout << "Stack(int)" << endl;
mpstack = new int[size];
}
// 析构
~Stack()
{
cout << "~Stack()" << endl;
delete[]mpstack;
mpstack = nullptr;
}
// 拷贝构造
Stack(const Stack &src)
:msize(src.msize), mtop(src.mtop)
{
cout << "Stack(const Stack&)" << endl;
mpstack = new int[src.msize];
for (int i = 0; i < mtop; ++i) {
mpstack[i] = src.mpstack[i];
}
}
// 赋值重载
Stack& operator=(const Stack &src)
{
cout << "operator=" << endl;
if (this == &src)
return *this;
delete[]mpstack;
msize = src.msize;
mtop = src.mtop;
mpstack = new int[src.msize];
for (int i = 0; i < mtop; ++i) {
mpstack[i] = src.mpstack[i];
}
return *this;
}
int getSize()
{
return msize;
}
private:
int *mpstack;
int mtop;
int msize;
};
Stack GetStack(Stack &stack)
{
Stack tmp(stack.getSize());
return tmp;
}
int main()
{
Stack s;
s = GetStack(s);
return 0;
}
运行结果如下。
Stack(int) // 构造s
Stack(int) // 构造tmp
Stack(const Stack&) // tmp拷贝构造main函数栈帧上的临时对象
~Stack() // tmp析构
operator= // 临时对象赋值给s
~Stack() // 临时对象析构
~Stack() // s析构
执行代码的过程中调用拷贝构造,将内存中的内容逐个拷贝,在 C++ 11 中可以借助右值引用实现移动拷贝构造和移动赋值来解决这个问题。
Stack(Stack &&src)
:msize(src.msize), mtop(src.mtop)
{
cout << "Stack(Stack&&)" << endl;
/*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
mpstack = src.mpstack;
src.mpstack = nullptr;
}
// 带右值引用参数的赋值运算符重载函数
Stack& operator=(Stack &&src)
{
cout << "operator=(Stack&&)" << endl;
if(this == &src)
return *this;
delete[]mpstack;
msize = src.msize;
mtop = src.mtop;
/*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
mpstack = src.mpstack;
src.mpstack = nullptr;
return *this;
}
执行结果如下。可以看到,在有拷贝构造和移动拷贝构造函数的时候,优先调用了移动拷贝构造和移动赋值。在移动拷贝构造和移动赋值中直接把资源所有权进行了转移,而非拷贝,这就大大提高了执行效率。
Stack(int) // 构造s
Stack(int) // 构造tmp
Stack(Stack&&) // 调用带右值引用的拷贝构造函数,直接将tmp的资源给临时对象
~Stack() // tmp析构
operator=(Stack&&) // 调用带右值引用的赋值运算符重载函数,直接将临时对象资源给s
~Stack() // 临时对象析构
~Stack() // s析构
右值引用在可以使重载函数变得更加简洁。右值引用可以适用 const T& 和 T& 形式的参数。
struct W
{
W(int&, int&) {}
};
struct X
{
X(const int&, int&) {}
};
struct Y
{
Y(int&, const int&) {}
};
struct Z
{
Z(const int&, const int&) {}
};
template
T* factory(A1& a1, A2& a2)
{
return new T(a1, a2);
}
template
T* factory_new(A1&& a1, A2&& a2)
{
return new T(std::forward(a1), std::forward(a2));
}
// demo
int a = 2;
int b = 2;
W* c = factory(a, b); // ok
Z* d = factory(2, 2); // 错误,2 是右值
W* pw = factory_new(a, b); // ok
X* px = factory_new(2, b); // ok
Y* py = factory_new(a, 2); // ok
Z* e = factory_new(2, 2); // ok
W* f = factory_new(2, 2); // 错误,
更多相关内容可以参考:c++——左值、右值、左值引用、右值引用
Object Oriented Programming, 面向对象是一种对现实世界理解和抽象的方法、思想,通过将需求要素转化为对象进行问题处理的一种思想。其核心思想是数据抽象、继承和动态绑定(多态)。
面向对象的意义在于:将日常生活中习惯的思维方式引入程序设计中;将需求中的概念直观的映射到解决方案中;以模块为中心构建可复用的软件系统;提高软件产品的可维护性和可扩展性。
1). 封装:
封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。
封装的意义在于保护或者防止代码(数据)被我们无意中破坏。
从封装的角度看,public, private 和 protected 属性的特点如下。
class Base
{
public:
Base(){};
virtual ~Base(){};
protected:
int int_pro;
};
class A : public Base
{
public:
A(){};
A(int da){int_pro = da;}
// 通过 obj 对象直接访问 protected 成员
void Set(A &obj){obj.int_pro = 24;}
void PrintPro(){cout << "The proteted data is " << int_pro <
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
2). 继承:
继承主要实现重用代码,节省开发时间。
子类可以继承父类的一些东西。
这里特别提一下虚继承。虚继承是解决C++多重继承问题(其一,浪费存储空间;第二,存在二义性问题)的一种手段。比如菱形继承,典型的应用就是 iostream, 其继承于 istream 和 ostream,而 istream 和 ostream 又继承于 ios。
3).多态:
多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为。与之相对应的编译时绑定函数称为静态绑定。多态是设计模式的基础,多态是框架的基础。
class A {
public:
A() {
m = new int[4]{ 1,2,3,4 };
std::cout << "constructor" << std::endl;
}
~A() {
if (m != nullptr) {
delete[] m;
}
}
A(const A& a) {
this->m = new int[4];
memcpy(a.m, this->m, this->len * sizeof(int));
std::cout << "copy constructor" << std::endl;
}
// 移动构造
A(A&& a) : m(a.m) {
a.m = nullptr;
std::cout << "move constructor" << std::endl;
}
// 赋值操作符重载
A& operator= (const A& a) {
memcpy(a.m, this->m, this->len * sizeof(int));
std::cout << "operator" << std::endl;
return *this;
}
private:
int len = 4;
int* m = nullptr;
};
A getA(A a) {
return a;
}
int main(void)
{
A a; // construct
A b = a; // copy construct
A c(a); // copy construct
A d; // construct
d = a; // operate
A e = getA(a); // construct, move construct
return 0;
}
对于栈对象或者全局对象,调用顺序与构造函数的调用顺序刚好相反,也即后构造的先析构。对于堆对象,析构顺序与delete的顺序相关。
基类采用虚析构函数可以防止内存泄漏。比如下面的代码中,如果基类 A 中不是虚析构函数,则 B 的析构函数不会被调用,因此会造成内存泄漏。
class A{
public:
A(){}
//~A(){}
virtual ~A(){} // 虚析构
};
class B : public A{
public:
B(){
// new memory
}
~B(){
// delete memory
}
};
int main(int argc, char *argv)
{
A *p = new B;
// some operations
// ...
delete p; // 由于基类中是虚析构,这里会先调用B的析构函数,然后调用A的析构函数
return 0;
}
但并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。
对于 class A,它的拷贝构造函数如下:
A::A(const A &a){}
循环调用。如果拷贝构造函数的参数不是当前类的引用,而是当前类的对象,那么在调用拷贝构造函数时,会将另外一个对象直接传递给形参,这本身就是一次拷贝,会再次调用拷贝构造函数,然后又将一个对象直接传递给了形参,将继续调用拷贝构造函数……这个过程会一直持续下去,没有尽头,陷入死循环。
只有当参数是当前类的引用时,才不会导致再次调用拷贝构造函数,这不仅是逻辑上的要求,也是 C++ 语法的要求。
拷贝构造函数的目的是用其它对象的数据来初始化当前对象,并没有期望更改其它对象的数据,添加 const 限制后,这个含义更加明确了。
另外一个原因是,添加 const 限制后,可以将 const 对象和非 const 对象传递给形参了,因为非 const 类型可以转换为 const 类型。如果没有 const 限制,就不能将 const 对象传递给形参,因为 const 类型不能转换为非 const 类型,这就意味着,不能使用 const 对象来初始化当前对象了。
如下图所示,C++的编译环境由如下几部分构成:C++标准库、C语言兼容库、编译器扩展库及编译模块。
#include //C++标准库,不带".h"
#include //C语言兼容库,由编译器厂商提供
值得注意的是,C语言兼容库功能上跟C++标准库中的C语言子库相同,它的存中主要为了兼容C语言编译器,也就是说如果一个文件只包含C语言兼容库(不包含C++标准库),那么它在C语言编译器中依然可以编译通过。
直接上代码吧。下面 f 和 g 是有问题的,这种情况就称为 Most vexing parse。
class A {
public:
A() { cout << "const without param" << endl; }
A(int a) { cout << "const with param" << endl; }
A(const A& b) { cout << "copy construct" << endl; }
};
int main(void)
{
A a; // const without param
A b(10); // const with param
A c = A(); // const without param
A d = A(10); // const with param
A e(d); // copy construct
A f();
A g(A());
A h{}; // const without param
A i{A{}}; // const without param
return 0;
}
问题在哪?
A f(); // 这个是不是可以看做声明了一个返回值为A的函数,函数名为 f,参数无
A g(A()); // 这个是不是可以看做声明了一个返回值为A的函数,函数名为 g, 参数类型为函数指针,这个函数指针的返回值类型为A,参数无
解决办法参考上面的 h, j。