前言

你可能听过很多种语言,什么,C,C++,JAVA,C#,HTML,CS,JS,GO,RUBY,JAVA,PHP,PYTHON…等等,但是会的却很少,觉得心里很慌,那我必须声明,完全没有这个必要,首先必须明确的是,无论是何种语言,都是工具,是一种计算机的语言,帮助你编写程序控制计算机实现功能。就好像英语俄语汉语马来西亚语日语一样,你没必要会所有语言。为什么要这么多种语言呢,答案很简单,每种语言有自己的优势和劣势,我们需要根据场景,选择合适的语言,比如我们要操作硬件,就选择更底层的C,我们要快速开发,就选择有大量库的Python,我们又要想要性能好又想写的快,我们就选择GO。C++从名字上就可以看出是对C的扩展,同样有比C++更高的优势,比如优秀的内存管理,以及其面向对象的特性。

问题

面对面试官的提问,永远不要说不会,大不了就一直想,看谁熬得过谁,而且可以发散思维,问的问题不懂尝试拐到别的地方去,重点是体现你的技能。

(1)什么是函数重载?

答:函数重载指的是用一个函数名定义不同函数,但要求这些函数的参数个数、参数顺序、参数类型中至少有一个不同才可构成函数重载,否则函数将冲突,函数声明将失败,这里要特别注意,仅函数的返回值不一样无法构成函数重载。

总结函数重载的三个规则:

  1. 函数名相同
  2. 参数个数相同,参数类型不同,参数顺序不同,均可以构成重载,但至少包含其中一个。
  3. 仅仅只有返回值类型不同不可以构成重载。

(2)什么是函数重写?

答:函数重写指的是派生类函数覆盖基类函数。在面向过程编程中为了增加代码的内聚程度,增加理解易度,与减少代码量经常会使用继承的方法,由于基类与子类各有不同的方法,如我们的基类水果,水果的吃法是一个方法函数,子类西瓜的吃法是切开吃,苹果的方法是削皮吃,因此子类必须重写父类的方法,根据自己的特点写吃法函数。

总结函数重写四个规则:

  1. 重写与被重写的函数属于继承关系,被重写的函数位于基类,重写的函数位于派生类。
  2. 函数名相同。
  3. 参数相同。
  4. 基类函数必须有virtual关键字。

(3)函数重定义(隐藏):

答: 函数重定义是指派生类和函数屏蔽了与其同名的基类函数,(其实这也是一种重写)。

重定义规则:

  1. 如果派生类的函数和基类的函数同名,但是参数不同,此时不管有无virtual,基类的函数被隐藏。
  2. 如果派生类的函数与基类函数同名,并且参数也相同,但是基类函数没有virtual关键字,此时,基类的函数都被隐藏。

(4)什么是enum:

答:enum是枚举类型,给出一系列固定的值,只能在里面选择一个,枚举类仅占一个整型的大小,节省空间,使代码更简洁更清晰。

#include <stdio.h>
 
enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
 
int main()
{
    enum DAY day;
    day = WED;
    printf("%d",day);
    return 0;
}

(5)什么是Sizeof

答:sizeof是C/C++中的一个操作符(operator),简单的说其作用就是 返回一个对象或者类型所占的内存字节数,sizeof是在编译时被直接给定了一个常值,可以理解成如宏定义一样被替换。

特点:

  1. 其不是函数,不需要包含任何头文件。
  2. 它的功能是计算一个数据类型的大小,单位为字节。
  3. 其返回值是size_t,在32位操作系统下是unsigned int,无符号整数。
(6)什么是double数组

答:在内存中申请一段连续的空间,空间被分成连续的dobule类型,可以根据下标直接访问一个double。

数组的原理:
在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来——称为数组。

注意数组首地址和数组第一个元素的首地址的概念是不同的,虽然二者值是一样,但是使用指针偏移时会默认数组指针偏移为一个数组的大小,而数组第一个元素偏移为一个元素的大小。

(7)构造、析构函数作用以及构造、析构函数的顺序

函数构造是一种特殊的函数(方法),再根据类创建时被被调用。与函数一样,构造函数可以重载。
构造函数总是在对象创建时被调用,这让构造函数成为类成员变量(int、指针)初始化的最好场所,意思就是由于类创建时会自动执行构造函数,因此我们可以将变量的初始化放在构造函数中执行。

构造函数与其他成员函数不同,它不需要其他用户来调用,而是在建立对象时自动执行。构造函数的名字必须与类名字相同,而不能由用户任意命名,以便编译系统能识别并将它作为构造函数处理。他不具备任何类型,不返回任何返回值。功能由用户自己定义,用户根据初始化的要求设计函数体和构造函数的参数。构造函数也可以通过手工调用,但是情况较少,并不能说不能自己调用。

构造函数一般分为如下几种:无参构造函数,带参数的构造函数,拷贝构造函数。

先举例无参构造与析构:

//构造函数(与类名相同)
//析构函数:没有参数也没有任何返回类型,被自动调用
#include<iostream>
using namespace std;
class Test
{
public:
	Test();//无参构造函数
	~Test();//析构函数:先创建的对象后释放
	void print()
	{
		cout << a << endl;
		cout << p << endl;
	}
private:
	int a;
	char *p;
};
 
Test::Test()//完成对属性的初始化工作
{
	a = 10;
	p = (char*)malloc(sizeof(100));
	strcpy(p, "Rita");
	cout<< "我是构造函数" << endl;
}
 
Test::~Test()
{
	if (p != NULL)
	{
		free(p);
	}
	cout << "我是析构函数,我被调用了" << endl;
}
//给对象搭建一个平台,研究对象的行为
void objplay()
{
	//先创建的对象后释放
	Test t1;
	t1.print();
	printf("分隔符\n");
	Test t2;
	t2.print();
}
int main()
{
	objplay();
	system("pause");
	return 0;
}

总结:先调用t1的无参构造函数,再调用print(),再调用t2的无参构造函数,再调用print(),最后先调用t2的析构函数,因为分配的内存在栈里,先入栈的后出栈,所以先定义的后释放,后调用析构函数。

三种构造函数的重载:

#include<iostream>
using namespace std;
//类的构造函数的分类:无参构造函数。有参构造函数。拷贝构造函数
class Test2
{
public:
	Test2()//无参构造函数
	{
		m_a = 0;
		m_b = 0;
		cout << "无参构造函数" << endl;
	}
	Test2(int a)
	{
		m_a = a;
		m_b = 0;
	}
	Test2(int a, int b)
	{
		m_a = a; m_b = b;
		cout << "有参构造函数"<< endl;
	}
	//copy构造函数
	Test2(const Test2& obj)
	{
		cout << "我也是构造函数"<< endl;
	}
public:
	void print()
	{
		cout << "普通成员函数"<< endl;
	}
private:
	int m_a;
	char m_b;
};
 
int main04()//调用无参构造函数
{
	Test2 t1;
	system("pause");
	return 0;
}
int main()//调用有参构造函数
{
	//1.括号法
	Test2 t1(1, 2);//调用参数构造函数,c++编译器自动调用
	t1.print();
	//2.=号法
	Test2 t2 = (1, 2, 3, 4, 5);//= c++对等号符功能增强,c++编译器自动的调用构造函数
	Test2 t3 = 5;
	//3.直接调用构造函数,手动的调用构造函数
	Test2 t4 = Test2(1, 2);//匿名对象的去和留,后面会说
	t1 = t4;//赋值操作:把t4copy给t1,对象的初始化和对象的赋值是两个不同的概念
 
	system("pause");
	return 0;
}

拷贝构造函数的调用:

#include<iostream>
using namespace std;
//构造函数的调用
class Test4
{
public:
	Test4()//无参构造函数
	{
		m_a = 0;
		m_b = 0;
		cout << "无参构造函数" << endl;
	}
	Test4(int a)
	{
		m_a = a; m_b = 0;
	}
	Test4(int a, int b)
	{
		m_a = a; m_b = b;
		cout << "有参构造函数" << endl;
	}
	//copy构造函数
	Test4(const Test4& obj)
	{
		cout << "我也是构造函数" << endl;
		m_a = obj.m_a + 100;//虽然是拷贝构造函数,也不是完全拷贝,可以根据自己的意愿拷贝(+100)
		m_b = obj.m_b + 100;
	}
public:
	void print()
	{
		cout << "普通成员函数" << endl;
		cout << m_a << m_b << endl;
	}
private:
	int m_a;
	int m_b;
};
 
int main06()
{
	Test4 t1(1, 2);
	Test4 t0(1, 2);
	t0 = t1;//不会调用拷贝构造函数
	//第一种调用方法
	Test4 t2 = t1;//用t1初始化t2,此时会调用拷贝构造函数
	t2.print();
 
	system("pause");
	return 0;
}
//第二种调用时机
int main()
{
	Test4 t1(1, 2);
	Test4 t0(1, 2);
	Test4 t2(t1);// t1初始化t2,此时会调用拷贝构造函数
	t2.print();
 
	system("pause");
	return 0;
}

关于匿名对象的去和留(匿名对象是否会被析构)


#include<iostream>
using namespace std;
class Location
{
public:
	Location(int xx = 0, int yy = 0)
	{
		X = xx;
		Y = yy;
		cout << "Constructor Object\n" << endl;
	}
	//copy构造函数完成对象的初始化
	Location(const Location& obj)
	{
		X = obj.X;
		Y = obj.Y;
	}
	~Location()
	{
		cout << "Destroyed\n" << endl;
	}
	int getX()
	{
		return X;
	}
	int getY()
	{
		return Y;
	}
private:
	int X;
	int Y;
};
//g()函数 返回一个元素
//结论1.函数的返回值是一个元素(复杂数据类型),返回的是一个新的匿名对象
//如果用匿名对象初始化另一个同类型对象,匿名对象转化成有名对象,不会被析构
//如果匿名对象赋值给另一个同类型对象,匿名对象被析构
Location g()
{
	Location A(1, 2);
	return A;//返回一个新的对象,没有名字
}
void objPlay1()
{
	g();
}
void objPlay2()
{
	//用匿名对象初始化m,此时c++编译器直接把匿名对象扶正,不会被析构
	Location m = g();
	cout << m.getX() << endl;
}
void objPlay3()
{
	//用匿名对象赋值给m2之后,匿名对象被析构
	Location m2(1, 2);
	m2 = g();//这不是初始化,这是赋值
	cout << m2.getX() << endl;
}
int main()
{
	//objPlay1();//运行结果1.Constructor Object2.Destroyed3.Destroyed
	//objPlay2();//运行结果1.Constructor Object2.Destroyed(析构A)3. 1  4.Destroyed(析构m)
	objPlay3();//运行结果1.Constructor Object(创建m2)2.Constructor Object(创建A)3.Destroyed(析构A)4.Destroyed(析构匿名对象)5.  1   6..Destroyed(析构m2)
	system("pause");
	return 0;

总结:所谓拷贝构造函数就是利用已经存在的对象创建新的对象。注意:拷贝构造函数必须在对象初创建的时候才会调用,一旦实现,就默认该函数不在生效。

而析构函数则是销毁对象时被调用的,作用是成员函数的清理,和构造函数类似。
简单总结下析构函数:

  1. 析构函数在对象销毁时自动调用,完成销毁的善后工作。
  2. 无返回值,与类同名。无参数,不可以重载与默认参数。
  3. 析构函数作用并不是删除对象,而是在对象雄安会之前完成一些清理工作。
(8)静态成员变量与成员函数的作用范围

答:静态成员属于整个类而不是属于某个对象,静态成员变量只存储一份供所有对象公用。所以在所有对象中可以共享它,使用静态成员变量多个对象之间的数据共享不会被破坏隐藏规则,保证了安全性还可以节省内存。

类的静态成员属于类也属于对象,最终归于类。

//声明:
static 数据类型 成员变量; //在类的内部

//初始化:
数据类型 类名::静态数据成员 = 初值; //在类的外部

//调用:
类名::静态数据成员
类对象.静态数据成员

注意,static成员只能类外初始化。
住:静态成员函数完成对静态数据成员的封装,此外静态成员函数只能访问静态成员,且不能用this指针(this 指针包含当前对象的地址),他属于类。

(9)友元是什么

需要定义一些函数,这些函数不是类的一部分,但又要频繁地访问类的数据成员,这时可以将这些函数定义该类的友元函数,除了友元函数外还有友元类,两者统称为友元。友元的作用是提高程序的允许效率(即减少类型检查和安全性检查的时间开销),但他破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员,因此友元可以是一个函数也可以是一个类。

(10)类的默认属性为私有属性。
(11)C++引用与指针的差别

相同点:

1. 都是地址的概念;

指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

区别:

1. 指针是一个实体,而引用仅是个别名;

2. 引用使用时无需解引用(*),指针需要解引用;

3. 引用只能在定义时被初始化一次,之后不可变;指针可变;

引用“从一而终” ^_^

4. 引用没有 const,指针有 const,const 的指针不可变;

5. 引用不能为空,指针可以为空;

6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;

typeid(T) == typeid(T&) 恒为真,sizeof(T) == sizeof(T&) 恒为真,但是当引用作为成员时,其占用空间与指针相同(没找到标准的规定)。

7. 指针和引用的自增(++)运算意义不一样;
(12)简述类和对象的关系

答:类是对象的抽象,对象是类的实体化。类是抽象的,而对象是具体的。

(13)简述函数模板和类模板

函数模板 针对仅参数类型不同的函数
类模板 针对仅数据成员和成员函数类型不同的类

.类模板语法

template <class T>

模板声明下面是函数就是函数模板,如果是类就叫类模板

一个类模板例子
下面写一个Person类 类模板,看看如何定义和如何使用类模板


#include <iostream>
#include <string>
using namespace std;
 
//类模板
template <class TypeName, class TypeAge>
class Person
{
public:
    Person(TypeName name, TypeAge age)
    {
        this->m_Name = name;
        this->m_Age = age;
    }
 
    TypeName m_Name;
    TypeAge m_Age;
 
    void showInfo()
    {
        cout<< "Name: " << m_Name << " Age: " << m_Age << endl;
    }
};
 
 
 
void test01()
{
    Person<string, int> p1("张三", 18);
    p1.showInfo();
}
 
int main()
{
    test01();
    system("pause");
    return 0;
}

模板就是重载函数的升级版,当有大量功能相似,类型不同,或者参数不同的功能函数要实现时,我们一般使用重载函数来实现,这要求我们去写大量函数,即不容易管理又显得凌乱,模板出现就是为了解决这个问题,通过定义一个泛型的类型,在编译的时候由编译器自动根据内容选择合适类型,就可以将上述大量函数简化为一个模板函数或者类模板。

===============================
template< class T >
template< typename T>

class与typename作用一致,class先出现后来为了防止与类混淆,C++使用typename替代class。这里的class不是指类,而是指数据类型。

=========================================================
函数模板:
#include “stdafx.h”
#include< iostream>
using namespace std;


	template <class T>
	T add(T A,T B )
	{
		T c = A + B;
		cout << "result :" << c << endl;
		return c;
	}
	
	int _tmain(int argc, _TCHAR* argv[])
	{
		
		add(1,3);
		system("pause");
		return 0;
	}

类模板:

类模板形式与函数模板答大体相似,要特别注意成员函数的定义和声明,在类外定义成员函数时,要把成员函数也模板化,无论是否使用到class T这个类型,成员函数都要模板形式定义。

#include "stdafx.h"
#include<iostream>
using namespace std;

template <class T>
class stu
{
public:
    T a,b;
    T ff(T a, T b);
    int dd(void);
};

template<class T>
int stu<T>::dd(void)
{
	cout << "hello" << endl;
	return 0;
}

	 template<class T> 
	 T stu<T>::ff(T a, T b)
	{
	    T c = a + b;    
	    cout << a + b << endl;
	    return c;
	}

int _tmain(int argc, _TCHAR* argv[])
{
    stu<int> a;
    a.ff(1,2); 
    a.dd();
    system("pause");
    return 0;
}
================================
输出:3 hello

记住模板是一种采用类型作为参数的程序设计,模板函数会直接推导参数类型,而模板类必须在定义时显示指定类型。

(14)虚函数的意义

函数是多态性的主要实现方式,利用虚拟函数和多态性,程序员可以处理普遍性而执行环境处理特殊性。即使在不知道一些对象的类型的情况下(只要这些对象属于同一继承层次并且通过一个共同的基类指针访问),程序员也可以命令各种对象表现出适合这些对象的行为。
上面说的是概念。
说白了就是,父类标记函数为虚函数,为普遍状态,子类可以选择重写或者是继续使用该函数,实现独立性。

(15)重载运算符必须用友元的有哪几个符号

流运算符<<与>>都需要进行友元重载。

(16)构造函数为啥可以被重载而析构函数不能。

因为构造函数可以有多个且可以带参数,而析构函数只能有一个且不能带参数。

(17)有如下class b,a[2],*c[2];问构造方法的执行次数?

执行了三次,因为指针不行构造。

(18)类的继承方式有几种?

私有继承、公有继承、保护继承

(19)赋值运算与拷贝构造的区别

拷贝构造函数与赋值运算的行为比较相似,都是将一个对象的值复制给另一个对象;但是其结果却有些不同,拷贝构造函数使用传入对象的值生成一个新的对象的实例,而赋值运算符是将对象的值复制给一个已经存在的实例。

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐