C/C++基础(不断更新)

这个专题的目的是在平时看书的过程中收集记录一些自己平时不会注意的小细节,所以会不断增加(我是有多么喜欢紫色,(⊙o⊙))


1. 对于全局变量,编译器一般默认初始化为0,但是局部变量是随机的,虽然如此,保持一个良好的初始化习惯是比较好的

要记录这个是因为如下题目:

#include <stdio.h>
union{
        int i;
        char x[2];
}a;
 
int main(){
 
	a.x[0]=10;
	a.x[1]=1;
	printf("%d \n", a.i);
 
}
刚看到这个题目的时候我迷惑的地方不是union的定义,也不是大端小端问题,而是int 高位的十六个bit位到底是啥?上面这个tip给出了答案,当然是0,于是,答案顺理成章就是266了。

2.常量指针不能赋值给非常量指针
首先需要说明一点的是,常量指针指的是指针所指对象是不能改变的。这句话单独来听你会由衷的赞同,但是实际应用的时候你可能就会“瞎”了眼,为什么我会这么说呢?请看下面的一个例子。
#include <iostream>
int main()
{
	char *const tt="adfd";
	tt[1]='M';
	std::cout<<tt;
}

这段代码是网上用来说明常量指针特性的,即常量指针一旦初始化之后就不能改变指向了,但是所指内容是可以改变的。乍一看没什么问题,但是运行的时候就会出问题,最开始我还觉得是不是c++不能够这么给char *赋值,然后发现下面一段代码是可行的:

#include <iostream>
int main()
{
	const char * tt="adfd";
	std::cout<<tt;
}

只能另找问题,才发现,是常量指针不能赋值给非常量指针.于是,我觉得这个例子应该这么写:

#include <iostream>
int main()
{
	char a[6]="dfsdf";
	char *const tt=a;
	tt[1]='M';
	std::cout<<tt;
}

3.一维数组初始化

如果定义时不指定数组大小,采用列表初始化:

int a[]={1,2,3};

如果定义时指定大小,若初始化列表的元素个数超出这个大小,报错,反之,填以默认值,整型数组默认为0


4.复杂指针的声明与理解

比如说:int (*(*(*func)(int*))[5])(int**)

想彻底的弄明白 int (*(*(*func)(int*))[5])(int**)这个式子,就要用Bruce Eckel大牛讲的“右左法则”,一左一右的分析一次,称为一步。
先看func知道,这个是个自定义的变量名字
 第一步 :
向右看,看到 ),再向左看,看到*,表示这个是个xx类型的指针变量
 第二步:
再向右看,看到(int *),现在我们看到的整体的式子是 (*func)(int*),说明它是一个函数指针,参数是个整型的指针
然后再向左看,看到 *,现在我们看到的整体的式子是 *(*func)(int*),说明他是个指向函数指针的指针,即这个函数指针的返回值是个指针类型
 第三步:
再向右看,看到),再向左看,看到(,到这里,我们看到的完整的式子是这样的:(*(*func)(int*)),意义跟上面的*(*func)(int*)一样
 第四步:
继续向右看,看到[5],现在我们看到的式子是(*(*func)(int*))[5],说明这个函数指针的返回值是个含有5个元素的数组的指针数组
再向左看,看到*
 第五步:
再向右看看到),再向左看,看到(,到这里,我们的完整的式子是:(*(*(*func)(int*))[5]),说明这个函数的返回值是个指向含有5个元素的指针数组的指针

 第六步:
再向右看,看到(int**),再向左看,看到int ,这时我们看到的就是这个完整的式子了int (*(*(*func)(int*))[5])(int**)

转自:http://hi.baidu.com/ngcezjjgvdeiope/item/41029f9135d3a9bccc80e566


5. 函数的返回值原理

今天在写由先序遍历结果和先序遍历结果恢复二叉树的时候,遇到了一个很奇怪的问题,如下

Node * create_BT_by_preorder_and_inorder_traversal(int *preorder,int *inorder,int preOrderStart, int inOrderStart,int len)
{
//cout<<"preOrderStart="<<preOrderStart<<" inOrderStart="<<inOrderStart<<" len="<<len<<endl;
if(len<=0)return NULL;


/*生成当前节点*/
Node *cur=(Node *)malloc(sizeof(Node));
if(cur==NULL){cout<<"no room for malloc"<<endl;return NULL;}
cur->val=preorder[preOrderStart];
cur->left=NULL;
cur->right=NULL;

int loc=find_value_in_array(inorder,len,inOrderStart,preorder[preOrderStart]);
cur->left=create_BT_by_preorder_and_inorder_traversal(preorder,inorder,preOrderStart+1,inOrderStart,loc-inOrderStart);
cur->right=create_BT_by_preorder_and_inorder_traversal(preorder,inorder,preOrderStart+loc-inOrderStart+1,loc+1,(len-(loc-inOrderStart)-1));
//return cur;//为什么我不用返回值也能得到正确的答案
}

问题就是最后那句return cur;

不管我加不加这句话,结果都是正确的,特别想知道为什么会是这样,今天查了好多资料还是没有找到具体的解答,暂且先做个记录,我是在g++下编译的,目前猜测是某种编译器优化,因为在vc下运行是不行的,需要加返回值。

因为查了一些资料,我想把收获记录下来:

1.VC返回值原理:http://www.claudxiao.net/2010/02/return_value_of_vc/

说的大概意思是这样的:返回值的为4字节是,函数将它的值赋给eax然后返回,如果为8字节,低四位赋给eax,高四位赋给edx,然后返回,如果为其他大小,调用者在调用之前,先压入一个4字节的(32位机器)的临时空间,这个临时空间指向一个数据段空间,用以将来存放返回值,进入被调用函数后,结果通过这个临时空间中存放的地址将结果存入,并将这个临时空间的地址用eax返回。


6. new operator 和 operator new 的区别

new operator就是我们平时用得比较多的new,例如:

A* a =new A();

此时的new 表示的就是new operator,不能被重载,而 operator new 是内存管理的操作符,会被new operator调用,可以被重载,一般来讲,new operator分以下几步:

1. 分配空间 :这一步就是operator new 干的活,返回一个指针,比如说ptr

2.调用构造函数在第一步分配的空间上构造对象

3.将ptr返回

一旦重载了全局的operator new ,就意味着new的第一步都变了,所以一般不要随便重载全局的operator new,可以在类内部重载operator new ,如:

class A
{
    public:
        A();
        ~A();
        void * operator new(size_t sz,string &message)
        {
            cout<<message<<endl;
            return malloc(sz);
        }
}

需要注意的是,重载后的函数返回值还是必须为void *,重载后的参数个数并不限制,但第一个参数必须是size_t ,编译器会自动找到这第一个,用需要的空间大小作为参数传入这第一个参数。另外,要在重载了operator new 的相同作用域内重载operator delete。

ps: 另有一种placement new,个人理解应该就是重载了全局operator new ,猜测的重载形式如下:

void * operator new(size_t sz,void *ptr)
{
    ....
    return *ptr;
}

本文参考: 淡淡的生活        ︶ㄣ第二名


7.C++和C 中struct区别?

C中struct只提供数据封装,不提供隐藏,所有数据可以直接访问,不能定义函数成员,C++中struct添加了类的概念,可以继承与被继承,但是不能定义显式的无参构造函数。(在阿里面试的时候被问到过)


8.一些有意思的C/C++问题


9.重载和重写的区别(overload和override的区别)

overload多发生在一个作用域内,函数签名不同,但是函数名相同,以实现函数的多个版本。

overrid多发生在继承体系中,函数签名一样,但是实现不同。

还有一种叫hide,也发生在继承体系中,比如如果不用using 声明一下的话,子类中同名的函数会隐藏父类中的函数。

参考1   参考2


10.C/C++面试题库
11.C++成员函数默认参数问题

如果成员函数声明的时候有默认参数列表,实现的时候函数定义不要加上默认参数,这样会造成编译错误,提示参数重复赋值。


12.内联函数和宏的区别

宏由#define预处理指令声明,是在预处理阶段的简单展开,而内联函数的展开是编译器控制实现的,内联函数是真正的函数,但是没有压栈的开销,所以效率较高,声明称内联不一定是内联,还需要给出函数体,而且函数体过大编译器可能不认账。

参考1 :内联函数和宏的区别   参考2:函数调用过程

参考1里面讲的挺详细的,但是有点需要补充:内联只是一种要求,不保证实现,如果函数体里面有循环和递归,一般就不能内联了,递归为什么不能内联很显而易见(无法展开)


13.写一个求出一个结构体某个成员偏移量的宏,比如如下结构体
struct A
{
    int m1;
    char m2;
    bool m3;
};

解答:#define offsetNum(Type,Member)   int (&((Type*)0->Member)

原文地址:https://www.cnblogs.com/obama/p/3045087.html