C/C++基础总结

1 static(静态)变量有什么作用

3个体明显的作用:1)在函数体内,静态变量具有“记忆”功能,即一个被声明为静态变量在一个函数被调用的过程中其值维持不变2)在模块内,它的作用域范围是有限制的,即如果一个变量被声明为静态的,那么该变量可以被模块内所有函数访问,但不能被模块外其他函数访问。3)内部函数应该在当前源文件中说明和定义,对于可在当前源文件以外使用的函数,应该在一个头文件中说明,使用这些函数的源文件要包含这个头文件。

与局部变量和全局变量的区别:static全局变量和普通全局变量相比的区别在于static全局变量只初始化一次,这样做的目的是为了防止在其他文件单元中被引用。static局部变量和普通局部变量的区别是static局部变量只被初始化一次,下一次的运算依据是上一次的结果值。static函数与普通函数的区别在于作用域不一样,static函数只在一个源文件中有效,不能被其他源文件使用。

为什么static变量值初始化一次:

对于所有对象(不仅仅是静态对象),初始化都只有一次,而由于静态变量具有“记忆”功能,初始化后,一直都没有被销毁,都会保存在内存区域中,所以不会再次初始化。(记住:初始化语句只执行一次)。

例如:

#include<stdio.h>

void fun(int i)
{
    static int value=i++;
    printf("%d\n",value);
}

int main()
{
    fun(0);
    fun(1);
    fun(2);
    return 0;
}
程序输出:
0
0
0

程序每次都输出0,是因为value是静态类型,只会定义一次。也就是说,不管调用fun()这个函数多少次,static int value=i++这个定义语句只会在第一次调用的时候执行,由于第一次执行的时候i=0,所以value也就被初始化为0了,以后调用fun()都不会再执行这条语句的

在头文件中定义静态变量,是否可行?为什么?


不可行,如果在头文件中定义静态变量,会造成资源浪费的问题,同时也可能引起程序错误。因为如果在使用了这个头文件的每个C 语言文件中定义静态变量,按照编译的步骤,在每个头文件中都存在一个静态变量,从而会引起空间浪费和程序错误。所以不推荐在头文件中定义任何变量,当然也包括静态变量。

2 const有哪些作用:

1)定义const常量,具有不可变性。

2)进行类型检查,是编译器对处理内容有更多的了解,消除了一些隐患。

3)避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。

4)保护被修饰的东西,防止被意外的修改,增强了程序的健壮性。

5)为函数重载提供参考

6)节省空间,避免不必要的内存分配

7)提高了程序的效率。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译器间的常量,没有了存储与读内存的操作,使得它的效率也很高。

为什么要使用const引用?

一般引用初始化一个左值的时候,没有任何问题;而当初始化值不是一个左值时,则只能对一个常引用赋值,而且这个赋值是有一个过程的,首先将值隐式转换到类型T,然后将这个转换结果存放在一个临时对象里,最后用这个临时对象来初始化这个引用变量。

const引用可以初始化为不同类型的对象或者初始化为右值,如字面值常量,而非const引用只能绑定到该引用同类型的对象。

3 volatile在程序设计中有什么作用?

volatile 是一个修饰符,它用来修饰被不同线程访问和修改的变量。被volatile类型定义的变量,系统每次用到它的时候都是直接从对应的内存当中提取,而不会利用cache中的原有数值,以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化。所以,volatile一般用于修饰多线程间被多个任务共享的变量和并行设备硬件寄存器等。

4 char str[]="abc";char str2[]="abc";str1与str2不相等,为什么?

两者不相等,是因为str1和str2都是字符数组,每个都有其自己的存储区,它们的值则是各存储区的首地址。

但是,对于const char *str3="abc"和const char *str4="abc";则不一样,str3和str4是字符指针而非字符数组,并不分配内存,其后的“abc”存放在常量区,str3和str4是指向它们指向的地址的首地址,而它们自己仅是指向该区首地址的指针,所以相等。

5 C++里面是不是所有的动作都是main()函数引起的

不是,对于C++程序而言,静态变量、全局变量、全局对象的分配早在main()函数之前已经完成。所以并不是所有的动作都是main()引起的,只是编译器是由main()开始执行的,main()只不过是一个约定的函数入口,在main()函数中的显示代码之前,会调用一个由编译器生成的_main()函数,而_main()函数会进行所有全局对象的构造及初始化工作。

在main()函数退出后再执行一段代码?答案依然是全局对象,当程序退出后,全局变量必须销毁,自然会调用全局对象的析构函数,所以剩下的就同构造函数一样了。

6 前置运算和后置元素有什么区别?

以++操作为例,对于变量a,++a表示取a的地址,增加它的内容,然后把值放在寄存器中;a++表示取a的地址,把它的值放入寄存器中,然后增加内存中a的值。前置(++)通常要比后置自增(—++)效率更高。

例题:a是变量,执行(a++)+=a语句是否合法?

首先我们要清楚两个概念:左值和右值。左值就是可以出现在表达式左边的值(等号左边),可以被改变,它是存储数据值的那块内存的地址,也称为变量的地址;右值是指存储在某内存地址中的数据,也称为变量的数据。左值可以作为右值,但是右值不可以是左值

本题不合法,a++不能当做左值使用。++a可以当作左值使用。++a表示取a的地址,对它的内容进行加1操作,然后把值放在寄存器中。a++表示取a的地址,把它的值装入寄存器,然后对内存中a的值执行加1操作。

7 new/delete与malloc/free的区别是什么?

malloc/free是C/C++语言的标准库函数,在C语言中需要头文件<stdlib.h>的支持,new/delete是C++的运算符。对于类的对象而言,malloc/free无法满足动态对象的要求,对象在创建的同时要自动执行构造函数,对象消亡的之前要自动执行析构函数,而malloc/free不在编译器控制权限之内,无法执行构造函数和析构函数。

具体而已,new/delete和malloc/free的区别:

1)new能够自动计算需要分配的内存空间,而malloc需要手工计算字节数。

2)new和delete直接带具体类型的指针,malloc和free返回void的指针。

3)new是类型安全的,而malloc不是。

4)new一般由两步构成,分别是new操作和构造。new操作对应于malloc,但new操作可以重载,可以自定义内存分配策略,不做内存分配,甚至分配到非内存设备上,而malloc不行。

5)new将调用构造函数,而malloc不能;delete将调用析构函数,而free不能。

6)malloc/free需要库函数stdlib.h的支持,而new/delete不需要

需要注意的是,有资源的申请,就有资源的释放,否则就会出现资源泄漏的问题,所以new/delete,malloc/free必须配对使用。而delete和free被调用后,内存不会立即收回,指针也不会指向空,delete或free仅仅是高诉操作系统,这一块内存被释放了,可以用做其他用途。但是,由于没有重新对这块内存进行写操作,所以内存中的变量数值并没有发生变化,出现野指针的情况。因此,释放完内存后,应该将指针指向置位空。

8 已知String类定义,如何实现其函数体。

String类定义如下:

 

class String
{
public:
    String(const char* str=NULL);
    String(const String &another);
    ~String();
    String &operator=(const String &rhs);
private:
    char* m_data;
};
String::String(const char *str)
{
    if(str==NULL)
    {
        m_data=new char[1];
        m_data[0]=‘\0‘;
    }
    else
    {
        m_data=new char[strlen(str)+1];
        strcpy(m_data,str);
    }
}

String::String(const String &another)
{
    m_data=new char[strlen(another.m_data)+1];
    strcpy(m_data,another.m_data);
}

String::~String()
{
    delete[] m_data;
}

String& String::operator=(const String &rhs)
{
    if(this==&rhs)
        return *this;
    delete[] m_data;
    m_data=new char[strlen(rhs.m_data)+1];
    strcpy(m_data,rhs.m_data);
    return *this;
}

 

9 栈空间的最大值是多少?

在Windows,栈是向低地址扩展的数据结构,是一块连续的内存的区域。栈顶的地址和栈的最大容量是系统预先规定好的,在Windows下,栈的大小是2MB。而申请堆空间的大小一般小于2GB.

由于内存的读取速度比硬盘快,当程序遇到大规模数据的频繁存取时,开辟内存空间很有作用。栈的速度快,但是空间小,不灵活。堆是向高地址扩展的,是不连续的内存区域。这是由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址的,而堆的大小受限于计算机系统中的有效虚拟内存,所以堆获得的空间比较灵活,也比较大,但是速度相对慢一些。

10 指针和引用的区别

程序设计中的引用其实就是别名的意思,它用于定义一个变量来共享另一个变量的内存空间,变量是一个内存空间的名字,如果给内存空间起另一个名字,那就能够共享这个内存了,进而提高程序的开发效率。指针执行另一个内存空间的变量,可以通过它索引另一个内存空间的内容,而指针本身也有自己的内存空间。

引用与指针有着相同的地方,即指针指向一块内存,它的内容是所指内存的地址,引用是某块内存的别名。但是,两者并非完全相同,它们之间也存在着差别,具体表现在以下几个方面:

1)从本质上讲,指针是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,即其所指向的地址可以被改变,其指向的地址中所存放的数据也可以被改变。而引用则只是一个别名而已,它在逻辑上不是独立的,它的存在具有依赖性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的,即自始自终只能依赖于同一个变量,具有“从一而终”的特性。

****2)作为参数传递时,两者不同。在C++语言中,指针与引用都可以用于函数的参数传递,但是指针传递参数和引用传递参数有着本质的不同

指针传递参数本质上是值传递的方式,它所传递的是一个地址值(所有对形参的改变都只是这个地址值中存放变量的改变,而存放这个地址值的指针是不会变化的。如果要改变存放该地址值的指针,需要传入的是该指针的地址,所以可以使用指针的指针或者指针的引用。)。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行的,不会影响主调函数的实参变量的值。

而在引用传递过程中,被调用函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被船里成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调用函数对形参的任何操作都影响了主调函数中的实参变量。虽然它们都是在被调用函数栈空间上的一个局部变量,但是任何对引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将影响不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量,那就得使用指向指针的指针,或者指针引用。

3)引用使用时不需要解引用(*),而指针需要解引用。

4)引用只能在定义时被初始化一次,之后不能被改变,即引用具有“从一而终”的特性,而指针却是可以改变的。

5)引用不可以为空,而指针可以为空。引用必须与存储单元相对应,一个引用对应一个存储单元。

6)对引用进行sizeof操作得到的是所指向的变量(对象)的大小,而对指针进行sizeof操作得到的是指针本身(所指向的变量或对象的地址)的大小。

7)指针和引用的自增(++)运算意义不一样。

8)如果返回动态分配的对象或内存,必须使用指针,引用可能引起内存泄漏。

11 指针和数组是否表示同一概念

主要表现在以下两方面的不同:

1)修改内容不同。

例如,char a[]="hello",可以通过去下标的方式对其进行修改,而对于char *p="word",此时p指向常量字符串,所以p[0]=‘x‘是不允许的。

2)所占字节数不同

例如,char *p="world",p为指针,则sizeof(p)得到的是一个指针变量的字节数,而不是p所指的内存容量。

char a[]="hello world";

char *p=a;

在32位机器上,sizeof(a)=12字节,而sizeof(p)=4字节。

但要注意的是,当数组作为函数参数进行传递时,该数组自动退化为同类型的指针。

13 野指针?空指针?

野指针是指指向不可用内存的指针。任何指针变量在被创建时,不会自动成为NULL指针(空指针),其默认值是随机的,所以指针变量在创建的同时应当被初始化,或者将指针设置为NULL,或者让它指向合法的内存,而不应该放之不理,否则就会称为野指针。而同时由于指针被释放(free或delete)后,未能将其设置为NULL,也会导致该指针变为野指针。虽然free和delete把指针所指的内存给释放掉了,但它们并没有把指针本身释放掉,一般可以采用语句if(p!=NULL)进行防错处理,但是if语句却起不到作用,因为即使p不是NULL指针,它也不指向合法的内存块。第三种造成野指针的原因是指针操作超越了变量的作用范围。

14 #include<filename.h>和#include"filename.h"有什么区别


对于#include<filename.h>,编译器先从标准库路径开始搜索filename.h,然后从本地目录搜索,使得系统文件调用较快。而对于#include"filename.h",编译器先从用户的工作路径开始搜索filename.h,后去寻找系统路径,使得自定义文件较快。

15 宏的总结

1)宏与函数的区别

a 函数调用时,首先求出实参表达式的值,然后带入形参。而使用带参数的宏只是进行简单的字符替换

b 函数调用在程序运行时处理的,它需要分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,也不进行值的传递处理,也没有“返回值”的概念。

c 对函数中的实参和形参都有定义类型,两者的类型要求一致。而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时带入指定的字符即可。

d 调用函数只可能得到一个返回值,而用宏可以设法得到几个结果。

e 使用宏次数多时,宏展开后源程序会变很长,因为每展开一次都是程序内容增长,而函数调用不会使源程序变长。

f 宏替换不占用运行时间,而函数调用则占用运行时间

g 参数每次用于宏定义时,它们都要重新求值,由于多次求值,具有副作用的参数可能会产生不可预料的结果。

2)枚举和define有什么不同

a 枚举常量是实体中的一种,而宏定义不是实体

b 枚举常量属于常量,而宏定义不是常量

c 枚举常量具有类型,但宏没有类型,枚举变量具有与普遍变量相同的性质,如作用域、值等,但宏没有。

d #define宏常量是在预编译阶段进行简单替换,枚举常量则是在编译的时候确定其值。

e 一般在编译器里,可以调试枚举常量,但是不能调试宏常量

f 枚举可以一次定义大量相关的常量,而#define宏一次只能定义一个

3)typedef和define的区别

a原理不同。#define是C语言中定义的语法,它是预处理指令,在预处理时进行简单的字符替换,不作正确性检查。typedef是关键字,它在编译时处理,所以typedef有类型检查的功能

b 功能不能。typedef用来定义类型的别名,这些类型可以是内置类型也可以是用户自定义的类型。#define不只是可以为类型去名字,还可以定义常量、变量、编译开关

c 作用域不同。#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而typedef有自己的作用域

d 对指针的操作不同。两者修饰指针类型时,作用不同。

4)宏定义与inline函数的区别

a 宏定义是在预处理阶段进行代码替换,而内联函数是在编译阶段插入代码;

宏定义没有类型检查,而内联函数有类型检查。

5)define和const的区别

a define只是用来进行单纯的文本替换,不分配内存空间,而const常量存在于程序的数据段,并在堆栈中分配了空间

b const常量有数据类型,而define常量没有数据类型

c 很多IDE支持调试const定义的常量,而不支持define定义的常量

16 C语言中struct和union的区别是什么

a 结构体与联合体虽然都是由多个不同的数据类型组成的,但不同之处在于联合体中所有成员共用同一地址空间,即联合体只存了一个被选择的成员,而结构体中所有成员占用空间是累加的,其所有成员都存在的,不同成员会存在不同的地址。

b 对于联合体的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了,而对结构体的不同成员赋值是互不影响的

17 C语言和C++中struct的区别

a C语言的struct不能有函数成员,而C++的struct可以有

b  C语言的struct中数据成员没有private、public和protected访问权限的设定,而C++的struct成员有访问权限限定。

c C语言的struct是没有继承关系的,而C++的struct有丰富的继承关系

18 C++中struct和class的区别

a class默认是private的,而struct默认是public的

b class可以用于定义模板,就像typename,而struct不可以

17 位运算总结

1)如何快速求取一个整数的7倍?

(X<<3)-X;

2)如何实现位操作求两个数的平均值

一般而言,求平均值可以使用(x+y)>>1,但是x+y可能移除,所以不使用加入,而使用异或和与运算实现加法:

(x&y)+(x^y)>>1

3)如何利用位运算计算数的绝对值?

以x为负数为例来分析,因为在计算机中,数字都是以补码的形式存在的,求负数的绝对值,应该是不管符号位,执行按位求反,末尾加1操作即可。

对于一个负数,将其右移31位后会变成0xffffffff,而对于一个正数而言,右移31位则为0x00000000,而0xffffffff^x+x=-1,因为任何数与1111异或,其实质都是把x的0和1进行颠倒计算。如果用变量y表示x右移31为,则(x^y)-y则表示的是x的绝对值。

18 考虑n为二进制,有多少个数中不存在两个相邻的1.

当n=1时,满足条件的二进制数为0、1,一共两个数;当n=2时,满足条件的二进制数有00、01、10,一共3个数;当n=3时,满足条件的二进制数有000、001、010、100、101,一共5个数。对n位二进制数,设所求结果a(n),对于第n位的值,分为0或者1两种情况:

1)第n位为0,则有a(n-1)个数。

2)第n位为1,则要满足没有相邻万为1的条件,第n-1位为0,有a(n-2)个数,因此得出结论a(n)=a(n-1)+a(n-2)

满足斐波拉契数列。

19 函数指针和指针函数的区别

指针函数是指带指针的函数,本质上是一个函数,函数返回类型是某一类型的指针。其形式一般如下所示:

类型标识符 *函数名(参数列表)

例如,int * f(x,y),它的意思是声明一个函数f(x,y),该函数返回类型为int型指针。

而函数指针是指向函数的指针,即本质是一个指针变量,表示的是一个指针,它指向的是一个函数。其形式一般如下所示:

类型说明符 (*函数名)(参数)

例如,int (*pf)(int x)它的意思就是声明一个函数指针,而pf=func则是将func函数的首地址赋值给指针。

引申:

1)数组指针/指针数组

数组指针就是指向数组的指针,它表示的是一个指针,它指向的是一个数组,它的重点是指针。例如,int(*pa)[8]声明了一个指针,该指针指向了一个有8个int型元素的数组。数组指针类似于二维i数组。即int a[][8];

指针数组就是指针的数组,表示的是一个数组,它包含的元素是指针,它的重点是数组。例如,int *ap[8]声明了一个数组,该数组的每一个元素都是int型的指针。

2)函数模板/模板函数

函数模板是对一批模样相同的函数的说明描述,它不是某一具体的函数;而模板函数则是将函数模板内的“数据类型参数”具体化得到的重载函数(就是由模板而来的函数简单地说,函数模板是抽象的,而模板函数则是具体的。

函数模板减少了程序员输入代码的工作量,是C++中功能最强的特性之一,是提高软件代码重用率的重要手段之一。函数模板的形式一般如下所示:

template<模板类型形参表>

<返回值类型> <函数名>(模板函数形参表)

{

  //函数体

}

其中<模板函数形参表>的类型可以是任何类型。需要注意的是,函数模板并不是一个实实在在的函数,它是一组函数的描述,它并不能直接执行,需要实例化成模板函数后才能执行,而一旦数据类型形参实例化以后,就会产生一个实实在在的模板函数了。

3)类模板/模板类

类模板与函数模板类似,将数据类型定义为参数,描述了代码类似的部分类的集合,具体化为模板类后,可以用于生存具体的对象

template<类型参数表>

class<类名>

{

  //类说明体

};

template<类型形参表>

<返回类型><类名><类型名表>::<成员函数1>(形参表)

{

  //成员函数定义体

}

其中<类型形参表>与函数模板中的一样,而类模板本身不是一个真实的类,只是对类的一种描述,必须用类型参数将其实例化为模板类后,才能用来生成具体的对象。简而言之,类是对象的抽象,而类模板就是类的抽象。

C++中引入模板类主要有以下5个方面的好处:

1)可用来创建动态增长和减少的数据结构

2)它是类型无关的,因此具有很高的可复用性

3)它在编译时而不是运行时检查数据类型,保证了类型安全

4)它是平台无关的,可移植性强

5)可用于基本数据类型

4)指针常量/常量指针

指针常量是指定义的指针只能在定义的时候初始化,之后不能改变其值。其格式为:

[数据类型][*][const][指针常量名称]

例如:char *const p1; int *const p2;(顶层const)

常量指针的值不能改变,但是其指向的内容却可以改变。

常量指针是指指向常量的指针,因为常量指针指向的对象是常量,因此这个对象的值是不能够改变的。定义的格式如下:

[数据类型][const][*][常量指针名称];

例如,int const *p; const int *p;

需要注意的是,指针常量强调的是指针的不可改变性,而常量指针强调的是指针对其所指对象的不可改变型,它所指向的对象的值是不能通过常量指针来改变的。

20 C++函数传递参数的方式有哪些

1)值传递

当进行值传递时,就是将实参的值复制到形参中,而形参和实参不是同一个存储单元,所以函数调用结束后,实参的值不会改变。

2)指针传递(实际上指针的值还是没有改变的,改变的只是指针中存放的地址所指向的变量,如果要改变指针的值,需要传递指针的引用或者指向指针的指针)

当进行指针传递时,形参是指针变量,实参是一个变量的地址,调用函数时,形参(指针变量)指向实参变量单元。这种方式还是“值传递”,只不过实参的值是变量的地址而已。而在函数中改变的不是实参的值,而是实参中存放的地址所指向的变量的值。

3)传引用

实参地址传递到形参,使形参的地址取实参的地址,从而使形参与实参共享同一单元的方式。

21 重载与覆盖有什么区别?

 

22 是否可以通过绝对内存地址进行参数赋值与函数调用

 

23 默认构造函数是否可以调用单参数构造函数

默认构造函数不可以调用单参数的构造函数。

 

 

 

 

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。