c/c++ 遇到的坑

如果C语言是0,那么C++是多少?
先来点个题,与本文其他内容无关。这可能是宇宙中最难以回答的问题了,它似乎牵扯到了哲学和伦理。不管怎样,C++起初都被认为是带类的C(C with classes),那他起码也得比C语言更厉害一点,那似乎应该是1。真的是这样吗?回到代码本身:
int c = 0;
printf("%d", c++);
这个……好像还是0!
函数声明被误以为创建对象
假设Foo是一个类,问以下代码中有多少个对象被创建:
Foo foo1();
Foo foo2(2);
Foo foo3[3];
Foo* foo4[3];
Foo& foo5 = foo2;
如果你被这个语法看花了眼,那一定是5个,因为你把第一行当成了用Foo类的默认构造版本创建对象,其实他只是一个返回值是Foo类型的无参函数的声明。注意,不同于new一个对象,new Foo;和new Foo();是等价的,在栈中用默认构造函数创建对象的话请务必去掉这对圆括号。
&& 和 || 的优先级
问以下代码执行完后,b的值是多少:
int a = 2, b = 1, c = 0;
bool result = a > 0 || ++b && c;
如果你整天把&&、|| 短路短路求值的特性记得滚瓜烂熟而忘了他俩的优先级这个最根本的东西,肯定会不假思索的回答1!
空指针调用对象的方法
老实说这个就是笔试题中的老鼠屎,你还得夹起来扔掉,要不然坏一锅粥,看代码:
class Foo {
public:
void func(int a) {
std::cout << a;
}
};

int main() {
Foo* pfoo = NULL;
pfoo->func(10086);

return 0;

}

正常人都以为这个会crash的,正常人谁会写这样的代码,但一码归一码,这代码是没问题的,因为这个func方法并没有引用到类实例的任何东西,他仅仅是接受一个参数,输出之。我们都知道,C++类成员函数在编译时其实就是加了一个类类型指针的普通C函数,对于上面的func成员函数,大概是这样的:
void func(Foo* instance, int a);
在这样调用时:pfoo->func(10086); 被编译器替换成了func(pfoo, 10086);,而func函数内并没用引用到pfoo的任何内存实例,所以自然就不会crash。
数组作为参数会退化为指针,那指定了长度的数组呢?
大家都知道,C/C++中数组作为函数的参数时会退化为同类型指针,所以无法在函数内直接通过这个指针获取数组的长度(C风格字符串除外),这也就是为什么那些C库里边操作数组的函数都得再传一个代表数组长度的参数,如qsort等。但是,如果给这个数组参数指定了长度呢?就像下面:
void foo(int a[10]) {
size_t size = sizeof(a); // 此时size多大呢?
}
这得从哲学的角度好好想一想,传int a[] 和 int* a时因为不知道数组在内存中的边界故而不清楚数组的长度,那么a退化成指针(即sizeof(a) == 4 or 8)似乎是一个不错的办法,让程序员自己想办法解决大小问题;但是现在指定了数组大小,是不是就意味着我了解了这个数组参数真正的、确切的长度,那么sizeof(a)是不是就是4 * 10 == 40呢?似乎从哲学上说的过去,那么事实真的是这样吗?很遗憾,并不是,a依然只是个int*,参数里的10在这里好像并没有什么用,除了让你自我感觉良好一点:看!我限制了传入数组的长度!但事实是你依然可以如此调用:
int a[3];
foo(a);
int* p = a;
foo§;
// 甚至……
foo(NULL);
就算你把整个天传进去,在函数体内sizeof(a)永远只是一个指针的大小!如果真的想限制数组参数的长度,那么有两种办法:

  1. 传指向指定大小数组的指针
    void foo(int (*a)[10]);
    现在就只能传大小为10的int数组了,但是得在调用时稍微改变一下,得加一个取地址操作符,即:foo(&a);
  2. 传指定大小的数组引用
    void foo(int (&a)[10]);
    完美解决你的问题!
    数组名和指针的关系
    课堂上老师总是说数组的名字就是指针,指向第一个元素的地址。于是:
    int a[3];
    int* p1 = a;
    int* p2 = &a;
    对于上面的代码,我当时认为这里面的p1和p2是等价的,对于数组的取地址操作(&)可有可无,就像引用一个函数一样:
    void foo() {};
    //…
    void(*f1)() = foo;
    void(*f2)() = &foo;
    事实上,这段对函数的引用是没有问题的,取地址操作确实是可有可无的,甚至在某些编译器上你都可以加任意个&,像这样:
    void(*p3)() = &&&&&foo;
    但是,对于数组来说,情况就完全不同了。

不加&的话表示一个指向数组第一个元素的指针,他的类型是int*,也就是老师常说的数组名就是指针;而加上&的话,就代表“取整个数组的地址”,他的类型不是普通的指针了,而是指向一个特定大小的数组的指针,对于上面来说,就是int()[3]了,对其解引用,得到的才是一个int,指向该数组的第一个元素。那么问题来了,这个类型能不能赋值给int* 类型的变量呢?毕竟他们都是指针,最终都是指向int的,答案当然是不行!所以上面的int* p2 = &a;是根本不能通过编译的,应该是int (*p2)[3] = &a;。

int的意思大概是:内存里有好多个int,我的任务就是指向其中一个。
int(
)[3]的意思大概是:内存中有好多3个int连在一起的空间,我的任务就是指向其中一个空间的开始。
这就好理解为什么对int()[3]解引用后便是一个普通的int指针,意思大概是:我int()[3]已经在内存中找到了3个int连续的空间了,确定到底要其中哪个int的任务就交给你int*吧!

上面这话的意思是这两种类型步进的偏移不同:32位环境下,int* 的偏移就是int的大小,即4,int(*)[3]的偏移是3个int的大小,即12。知道了这两种区别,就不会做错下面这道面试题了:
int a[5] = {1,2,3,4,5};
printf("%d,%d", *(a + 1), ((&a + 1) - 1));

(a + 1)很好理解,就是数组的第2个元素,即2;重点是((&a + 1) - 1),先看&a + 1,前面说过了,&a代表指向有5个元素的数组的指针,他的步进偏移是5 * 4 = 20,所以&a + 1可以看成是代表这个数组后面的那个相同大小数组的地址,比a的地址高20个字节,然后给他解引用,前面说过,得到了指向int的指针,此时这个指针指向数组a后面的那个数组的第一个元素,而实际情况是a后面并没有数组了,若是此时对这个指针进行解引用,那么要么得到一个随机值,要么直接崩溃。接着,对解引用出来的int 减了个1,int*的偏移是4,故向前偏移4个字节,这不正好指向了数组a的最后一个元素吗?解引用之,得到5。

你还会发现,这个类型不就是二维数组的数组名的类型吗?像这样:
int a[2][3];
int (*p)[3] = a;
而对二维数组取地址,结果就又是另外一种情况了……好了,就此打住。
构造函数中成员变量的初始化顺序
分析这段代码的输出:
class Foo {
int n1;
int n2;
public:
A() : n2(0), n1(n2 + 1) {}
void print() {
std::cout << n1 << “,” << n2 << std::endl;
}
}
// …
Foo foo;
foo.print();

如果不清楚C++类构造函数中成员变量的初始化顺序,那么很容易得出答案:n1是1,n2是0。事实上,构造函数中成员变量的初始化顺序和初始化列表中指定的顺序并无关系,只与成员在类中的声明顺序有关。
在上例类Foo中,n1先于n2声明,故在构造Foo的对象时,n1也先于n2构造,即使初始化列表中试图先构造n2。因为构造n1时:n1(n2 + 1),n2并没有被构造,所以n1的值是未知的,n2的确被初始化为0。

原文:https://blog.csdn.net/u014417133/article/details/77148892

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值