C/C++程序设计面试知识点整理

文章目录

  • C/C++程序基础
    • 赋值语句
      • 基本数据类型赋值
    • 操作符、运算符:说明特定操作的符号(最底层函数)
      • 1、算术运算符
      • 2、关系运算符:
      • 3、逻辑运算符
      • 4、位运算符
      • 5、赋值运算符
      • 6、其他运算符:sizeof、三目等
      • C++特有:域操作符、new/delete、利用&实现引用
    • 关键字:系统预定义的具有特殊含义的单词。
      • C++特有:string、bool
  • -------------------------------------------------------------------------------------------------
  • 面向对象三大特征
    • 一、封装:隐藏对象的属性和实现细节,仅对外公开接口
    • 二、继承 :使得子类具有父类的属性和方法或者重新定义等
    • 三、多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
      • 1、静态多态与重载:静态多态通过函数的重载来实现(包括运算符重载)。
        • 重载:同一个类中两个或两个以上同名方法
      • 2、动态多态:程序运行过程中才动态地确定操作所针对的对象,通过虚函数和指针来实现
    • 类:C++ 的核心特性,通常被称为用户定义的类型。
      • 成员函数
        • 静态成员函数:只能访问静态数据成员
      • 构造、析构函数(特殊的成员函数、方法)
        • new与delete调用构造与析构
      • 类访问修饰符
        • 继承中的特点
    • 友元函数、类
  • 类对象与类指针:
  • 内存问题
    • 成员变量的内存空间
  • 类的交叉引用

C/C++程序基础

赋值语句

基本数据类型赋值

int x=3,y,z;
x*=(y=z=4); printf("x=%d\n",x);

1、x的值为3,y和z未被初始化.

2、首先执行z=4,然后执行y=z,最后执行x*=y,x=3*4=12


操作符、运算符:说明特定操作的符号(最底层函数)

除了常见的三大类,算术运算符、关系运算符与逻辑运算符之外,还有一些用于完成特殊任务的运算符,比如位运算符。

C语言的运算符主要用于构成表达式.
相同优先级运算符,从左至右依次运算。注意后缀运算优先级高于前缀。因此++i++应解释为++(i++)。
而与或非的运算优先级都不一样,因此a && b || b && c解释为(a && b) || (b && c)


1、算术运算符

C/C++程序设计面试知识点整理_第1张图片

前缀运算符是在使用变量前先进行运算,后缀是使用后再进行运算。



2、关系运算符:

关系运算符的值只能是0或1。关系运算符的值为真时,结果值都为1。关系运算符的值为假时,结果值都为0。

如 ==是关系运算符,用来判断两个值大百小是否相同,当左边的内容与右边的内容相同时,返回1,其余时候返回0。
C/C++程序设计面试知识点整理_第2张图片



3、逻辑运算符

C/C++程序设计面试知识点整理_第3张图片


4、位运算符

(1)按位与运算符(&):参加运算的两个数,按二进制位进行“与”运算。
运算规则:只有两个数的二进制同时为1,结果才为1,否则为0。(负数按补码形式参加按位与运算)

0 & 0= 00 & 1= 01 & 0= 01 & 1= 1。

例:3 &500000011 & 00000101 = 00000001 ,所以 3 & 5的值为1

(2)按位或运算符(|):参加运算的两个数,按二进制位进行“或”运算。

运算规则:参加运算的两个数只要两个数中的一个为1,结果就为1。

0 | 0= 0 ,  1 | 0= 10 | 1= 1  ,  1 | 1= 1 。

例:2 | 400000010 | 00000100 = 00000110 ,所以2 | 4的值为 6

(3)异或运算符(^):参加运算的两个数,按二进制位进行“异或”运算。

运算规则:参加运算的两个数,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。

0 ^ 0=00 ^ 1= 11 ^ 0= 11 ^ 1= 0 。

例: 2 ^ 400000010 ^ 00000100 =00000110 ,所以 2 ^ 4 的值为6

(4)取反运算符(~):按二进制位进行"取反"运算。


(5)二进制左移运算符(<<): 将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。

(6)二进制右移运算符(>>):将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。


5、赋值运算符

C/C++程序设计面试知识点整理_第4张图片



6、其他运算符:sizeof、三目等

C/C++程序设计面试知识点整理_第5张图片



C++特有:域操作符、new/delete、利用&实现引用

1、域操作符::
在类外部声明成员函数。void Point::Area(){};
调用全局函数、变量;
表示引用成员函数变量及作用域,作用域成员运算符 例:System::Math::Sqrt() 相当于System.Math.Sqrt()
调用类的静态方法: 如:CDisplay::display(…)


2、new/delete(C++运算符):申请、释放动态内存

对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。
C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete.

由于内部数据类型的“对象”没有构造与析构的过程,与malloc/free等价
new/delete必须配对使用,malloc/free也一样




关键字:系统预定义的具有特殊含义的单词。

1、数据类型:
基本数据类型、指针类型、构造类型(数组和结构体)、空类型


2、其他:
return、


extern:
一、告知编译器:当extern与“c”一起使用的时候,就是告诉编译器,下面的函数或者变量以C语言的方式编译。
二、共享:extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。


break、 continue auto

sizeof(既属于操作符,也属于关键字)



const
(1)定义常量
(2)修饰引用传参(自定义数据类型,防止引用对象不小心改变了对象)
(3)修饰函数返回值(返回值在静态区),则返回值只能赋值给同为const的变量
(4)修饰成员函数(成员函数后加 const),表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变。

规则:const离谁近,谁就不能被修改;

int * const p ;   指针常量,表示指针变量本身不能被修改,只能指向一个地址。
const int *p;      常量指针,指针指向的内容为一个常量。

C++中,被const修饰的变量,会在编译期间将对应的变量,直接替换为该变量的值。但是C语言中不会。



static
(1)静态全局变量作用域只在定义该变量的源文件。全局变量作用域是整个源程序(可能为多个源文件)(作用域不同·)
(2)静态局部变量只能被其作用域内的变量和函数进行访问使用,数据存放在全局数据区,只初始化一次。(存储方式不同)
(3)静态函数作用域仅在本文件(静态函数在内存只有一份,代码区?),非静态函数每个调用保持一份复制品。

类的静态成员和方法不属于类的实例,而属于类本身并在所有类的实例间共享。
类中不可以初始化,只能在类外全局作用域初始化(默认为0),静态成员可以独立访问,无需依赖任何对象的建立(通过域操作符)。


final

typedef

if(非零即执行)

switchcase

循环结构



C++特有:string、bool

C中有字符数组或字符串常量指针来替代字符串


bool仅有0/1,赋值非零则值为1(原理应该是判断表达式)

bool a=0.7; //a=1

-------------------------------------------------------------------------------------------------


面向对象三大特征

一、封装:隐藏对象的属性和实现细节,仅对外公开接口

将客观事物抽象成类,每个类对自身的数据和方法实现protection(public、private、public)

封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性 的访问级别;

将抽象得到的属性和(属性上的)方法相结合,形成一个有机的整体,形成“类”,其中数据和函数都是类的成员。

封装可以隐藏实现细节,使得代码模块化



二、继承 :使得子类具有父类的属性和方法或者重新定义等

继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。

继承是使代码可以复用的重要手段,也是面向对象程序设计的核心思想之一。

继承就是不修改原有的类,直接利用原来的类的属性和方法并进行扩展。原来的类称为基类,继承的类称为派生类,他们的关系就像父子一样,所以又叫父类和子类。

一般格式如下(默认继承为private):

class Shape
{......};

class Triangle : public Shape
{......};



三、多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。

多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a.编译时多态性:通过重载函数实现
b 运行时多态性:通过虚函数实现。


1、静态多态与重载:静态多态通过函数的重载来实现(包括运算符重载)。

要求在程序编译时就知道调用函数的全部信息。速度高、效率快,缺乏灵活性,在程序运行前就决定了执行的函数和方法。

重载:同一个类中两个或两个以上同名方法

重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。

类的关系:重载是同一个类中方法之间的关系,是水平关系。
产生方法:重载是多个方法之间的关系。


2、动态多态:程序运行过程中才动态地确定操作所针对的对象,通过虚函数和指针来实现

同一类族中不同类的对象,对同一函数调用做出不同的响应。

如在动物类中 定义一个抽象类类eat()
动物类的子类和父类同时重写eat()方法、就体现了接口的不同实现形式




类:C++ 的核心特性,通常被称为用户定义的类型。

类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。
类中的数据和方法(函数)称为类的成员。


1、类定义:类定义后必须跟着一个分号或一个声明列表

class Box
{
   public:
      double length;   // 盒子的长度
};

2、定义对象:

Box box1;          // 直接声明 Box1,类型为 Box

Box * box1 = new Box();  //动态内存分配

3、访问数据成员:类的对象的公共数据成员可以使用直接成员访问运算符 (.) 来访问

 box1.breadth = 13.0;



成员函数

1、成员函数声明(不实现)

 double function(void);// 返回体积

2、成员函数 类内实现

class Box
{
      double getVolume()
      {
         return length * breadth * height;
      }
};

也可以在类的外部使用范围解析运算符::定义该函数,如下所示:

double Box::getVolume(void)
{
    return length * breadth * height;
}



静态成员函数:只能访问静态数据成员

意义:不在于信息共享、数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。

与非静态成员函数的区别:
1)非静态成员函数在调用时 this指针被当做参数传进。

2)静态成员函数属于类,不属于对象,没有this指针,所以不能访问本来的非静态成员,但是可以引用本类中的静态数据成员,如果一定要引用本类中的非静态成员,需要加上对象名和成员运算符.

3)类外调用公用的静态成员函数要使用类名和域运算符::

构造、析构函数(特殊的成员函数、方法)

1)构造、析构函数的名称与类的名称相同。为了区分,析构函数名字前面有~
2)都无返回值
3)构造、析构函数不能直接调用 (像调用其他成员函数一样)
初始化类时自动调用构造函数, 对象退出作用域时(如所在的方法结束时),由系统自动调用析构函数释放对象。
可以通过new与delete调用。

class stu
{
构造函数: stu()
析构函数:  ~stu()
}

构造函数:用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。

1)构造函数可以有多个参数
2)构造函数可以重载(可以有多个构造函数),可根据其参数个数的不同或参数类型的不同来区分它们。
自定义构造函数会覆盖默认构造函数。或者定义了自定义构造函数后,编译器是不会创建默认无参构造函数的



析构函数:完成与构造函数相反的工作,对象退出生命周期时,完成清理的工作如释放内存
1)析构函数无参数
2)析构函数不可重载,每一个类有且只有一个析构函数
3)对象退出生命周期时,编译器自动调用析构函数,也可以人为调用析构函数(通过new),意义不大。
4)当类中有动态内存分配时,需要增加自定义的析构函数,否则有可能导致内存泄漏。

在函数栈空间生成一个对象,然后获取他的地址,函数退出栈清空以后,对象不在了,这个时候就执行了析构函数。

如果在堆空间,你定义了一个变量对象。用指针或者引用保存了对象地址。如果你没有手动进行销毁,那么在程序结束前,地址一直有效。他是一直存在的。也就不会调用析构函数。
局部指针变量开辟在栈上,指针内容开辟在堆上。



new与delete调用构造与析构

void test(){
    haitao * pt; //动态的创建一个对象指针
    pt = new haitao();
    delete pt; // 必须使用delete释放指针所指向的内存空间,否则会内存泄漏
}



类访问修饰符

数据封装是面向对象编程的一个重要特点,它防止函数直接访问类的内部成员。
类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。
关键字 public、private、protected 称为访问修饰符。

类中成员的默认访问属性和默认继承属性都为private。
结构体默认访问属性和默认继承属性都为public。

一个类可以有多个 public、protected 或 private 标记区域。
每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。
成员和类的默认访问修饰符是 private。

class Base {
 
   public:
 
  // 公有成员
 
   protected:
 
  // 受保护成员
 
   private:
 
  // 私有成员
 
};

公有(public)成员:在程序中类的外部是可访问的。可以不使用任何成员函数来设置和获取公有变量的值

class Box
{
public:
    int length;
};

int main()
{
Box b1;
b1.length=1;
cout<<b1.length;
}

私有(private)成员:只能被本类成员(类内)和友元访问,不能被派生类访问。默认情况下,类的所有成员都是私有的

class Box
{
int length;
};

int main()
{
Box b1;
b1.length=1;
cout<<b1.length;
}

保护(protected)成员:只能被类成员函数、友元访问和派生类(成员函数)访问,派生类对象也不能(在类外)访问

class Father
{
 protected :
     int m=5;
};


class Son : Father
{
public:
    void fun()
    {
    this->m=3; //等同于m=3,正确,可以在派生类内访问
    cout<<m;  //输出3
    }
};


int main()
{
Son b1;
b1.fun();
b1.m;  //错误,m不可以在类外访问
}

继承中的特点

有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。

1.public 继承:基类 成员访问属性在派生类中保持不变。

2.protected 继承:基类 public 成员在派生类中变成protected成员,其他不变。

3.private 继承:基类成员访问属性在派生类中都变为private属性。

但无论哪种继承方式,上面两点都没有改变:

1.private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;

2.protected 成员可以被派生类访问。



友元函数、类

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:

class Box
{
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};

声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置如下声明:

friend class ClassTwo;



类对象与类指针:

两者不可相提并论,类对象不可理解成指针,虽然实现机制很像。

1、类对象:利用类的构造函数(构造函数:对类进行初始化工作)在内存中分配的一块区域(包括一些成员变量赋值);

2、类指针:一个内存地址值,指向内存中存放的类对象(包括一些成员变量所赋的值).

要发挥虚函数的强大作用,必须使用指针来访问对象. 指针可以实现动态多态,直接用对象不行

区别与特征:

(1)在类的声明尚未完成的情况下,可以声明指向该类的指针,但是不可声明该类的对象…

(2)父类的指针可以指向子类的对象…

(3)定义对象实例时,分配了内存。指针变量则未分配类对象所需内存

(4)指针变量是间接访问,但可实现多态(通过父类指针可调用子类对象),并且没有调用构造函数。
直接声明可直接访问,但不能实现多态,声明即调用了构造函数(已分配了内存)。


内存问题

成员变量的内存空间

成员变量并不能决定自身的存储空间位置。决定存储位置的对象的创建方式。

1、如果对象是函数内的非静态局部变量,则对象,对象的成员变量保存在栈区。

class A {
   private:

      void * p;  
      
  public :

      A(){ p = malloc(1000);}
};

A a;

a 分配在栈上,变量 p 也分配在栈上,但是 p 指向的内存空间分配在堆上。
(malloc是C中动态创建堆空间的函数)



2、如果对象是全局变量 或 函数内的静态局部变量,则对象,对象的成员变量保存在静态区。



3、如果对象是new出来的,则对象,对象的成员变量保存在堆区。

int main(){
A * a = new A(); //动态内存分配,a在栈上,指向的对象空间在堆上(栈上一个指针变量a,指向在堆上的一个A类型的对象)
}



类的交叉引用

什么是交叉引用?
简单来说就是 A类中包含B类的对象,B类中包含A类的对象。

class A{
	B b;
};

class B{
	A a;
};

为什么产生交叉引用?
因为编译器如果先编译 A,那么 A 中引用了 B,B 还没有编译所以不能先编译 A,相反如果先编译 B ,B 中引用了 A,A 还没有编译,所以也不能先编译 B。于是编译器就迷茫了。


解决办法:


using namespace std;

// 先声明再创建,解决交叉引用
class A;
class B;

class A{
public:
	// 声明为指针类型,解决交叉引用
	B* b;
	int value;
};

class B{
public:
	// 声明为指针类型,解决交叉引用
	A* a;
	int value;
};

int main()
{
	// 初始化
	A a;
	B b;
	a.value = 1;
	b.value = 2;
	a.b = &b;
	b.a = &a;
}



你可能感兴趣的:(面试,C语言)