- static和extern区别?
外部是可见性关键词,内部是所有实例共享static的变量,静态类成员变量属于全体实例,所以任意实例不能给静态变量分配内存,要用类使用变量,static让被修饰的语句具有唯一性,确定性
- 为什么要在类内设置static?
如果没有设定为static,连接器会跨编译单元进行连接(外部链接),尽可能让函数和变量变成静态(内部链接),除非需要跨编译单元进行连接。不能在静态方法中访问实例化对象里的非静态变量;static可以将被修饰的生存期延长到整个程序,静态成员只需要一个初始化,而没有静态的成员每次调用都会初始化
- 构造对象时都会调用构造函数
- 为什么要写析构函数?
在对象被销毁时被调用,想到于给一个反馈,比如~Entity(){std::cout«“Destroyed Entity!"«std::endl;}
- 可见性
private(只有这个类可以访问这些变量),public,protected(在层次结构中的子类可以访问这些符号)
- 为什么要用可见性,哪里要用到,为什么所有不用public?
可见性或许和性能无关,不是CPU需要理解的,但是增加代码的维护性,例如private规定一些东西不能被破坏
- 比较int example[5]和int* another = new int[5],为什么要动态的用new来分配,而不是在栈上创建?
生存期。用new在堆上创建,将一直存在直到手动回收
- 为什么使用mutable?
在类中修改特定成员变量(const)
- 什么时候用栈,什么时候用堆?
栈:作用域结束,内存释放,栈内存回收,如果想让在作用域之外依然存在,就不能分配到栈上,如果类/对象太多,栈没有足够的空间,分配在堆上(例:Entity* entity=new Entity(“Cherno”)),显式的控制对象的生存期,但是麻烦,需要手动delete,可能会导致内存泄露,而栈方便自动
- 创建new的过程
找到一个足够大的内存块,给一个指向那块内存块的指针(内存地址 ),调用构造函数,记得delete!
this是一个常量指针,但是可以修改this指向对象的内容
- 智能指针的本质?为什么要用智能指针?
原始指针的包装,调用new并分配内存,基于使用的智能指针,自动释放。好处,不会得到没有引用的悬空指针,从而造成内存泄露,在unique_ptr中不调用new是因为它安全,而shared_ptr需要分配另一块内存,叫做控制块,用来存储引用计数,当引用计数为0时shared_ptr不存在;有限使用unique_ptr,但在对象之间共享,不能使用unique_ptr时,就使用shared_ptr。
- 为什么要重写C++中存在的数据结构?
标准模板库的速度不是优先考虑的东西
- 关于动态数组?
std::vector
- 为什么存储vertor对象比存储指针在技术上更优?
vertor内存分配时连续的数组,没有内部碎片,更方便读取,但是vector读取更慢,指针直接指向内存地址,更快
- vector如何重新分配多余的元素?
从内存中的就位置复制到内存中的新位置,然后删除旧位置的内存,
- 静态链接和动态链接的实际性能差异?
静态链接允许更多优化发生,dll是动态链接库,lib文件其实就是指向ddl文件的指针,如果用不同的静态库,在运行时连接到ddl,会得到不匹配的函数
-
C++中如何处理多返回值
-
C++模板,在哪里可以使用模板?在哪里不可以使用?
模板允许用户定义一个可以根据用途进行编译的模板,就是让编译器基于一个规则为人写代码
template <typename T>
void Print(T value){
std::count << value <<std::endl;
}
Print<std::string>(5)
T被string所替代
模板只有在它被调用时才会被创建,因为它只是代码,才会被创建,报错取决于编辑器
template<T N>
class Array{
private:
T m_Array[N];
public:
int GetSize() const { return N;}
}
日志系统,包含不同类型的统一缓冲区时,但是模板变复杂容易找不到错误在哪
- C++中的堆与栈是如何分配内存的?
int a=5;
int* b =new int;
*b =5;
shift,又是堆栈
应用程序启动后,操作系统要做的就是将整个程序加载到内存,并分配一大堆物理RAM,堆与栈是RAM中实际存在的两个区域,栈通常是一个预定义大小的内存区域,2M左右,堆也是预定义了默认值,但可以生长。我们的内存中有两个不同的区域,内存是用来实际存储数据的,需要另一个存储运行所需的数据,不管是局部变量还是从文件中读取的东西,当我们想要存储一个整数时,把栈指针移动4个字节,(就像一条CPU指令?)所以栈分配很快,而堆调用malloc的函数,浏览空闲列表,给一个指针,记录。
- auto自动推导类型,那么好用,为什么不全部用auto?
迭代器声明可以用用,有些类型需要明确语义,如接口返回值,避免隐式转换
- 静态数组std::array
在栈上创建,有边界检查
- lambda
在我们会设置函数指针指向函数的任何地方,都可以将它设置为lambda
- 为什么需要结构体?
struct Object{
int weight;
int value;
} e[array_length];//一个类型为Object的数组
const Object a;
限制了成员元素的使用,避免混淆,有需要用到value[],Value[],不同的结构体可以拥有相同名字的成员元素,同名的成员元素相互独立。访问/修改成员元素(*ptr).v=tmp,ptr->v=tmp修改结构体中的成员元素
- union和struct区别?
union所有成员共享内存,而struct每个成员有独立内存,union大小=最大成员大小,struct大小>=所有成员大小之和,union修改一个成员会影响其他成员,struct修改以一个成员不影响其他成员
- 一些指针注意事项?
指针变量的大小在不同环境下有差异。在 32 位机上,地址用 32 位二进制整数表示,因此一个指针的大小为 4 字节。而 64 位机上,地址用 64 位二进制整数表示,因此一个指针的大小就变成了 8 字节。
int* pa = &a;
(*px).a=4;
px->b =5;
C++程序基本要素
程序由语句组成,包括声明语句和执行语句
语句由基本要素组成:标识符,关键字,常量(不占空间),变量,运算符,表达式
C++对C的扩充
- C++函数声明不可以省略
- 变量声明语句不要求放在函数和语句块的开始位置
- 强制类型转换,增加形式 int(a),类似函数调用
- C++用new和delete运算符取代C中的malloc()和free()
- C++提供字符串类string,替代C字符数组
- C++用控制台输入输出流对象(iostream)替代C的stdio函数库,
数据类型
- 基本数据类型:
从低级到高级是自动转换,高级转低级会有风险,因为会丢掉一部分
强制转换不安全,所以要指明,如
float x=123.56;
int i =(int)x
- 为什么要有变量的类型?可以没有吗?
变量类型确定长度,变量名确定起点,有了起点和长度,可以知道在内存中取的地址。如果一门语言设计的可以从其他地方知道终点,那就可以没有变量类型
数组
一组具有相同类型数据的有序集合
- 如何处理不同类型数据?
结构体
- 数组的维数为什么是常量表达式?
因为要分配给定的空间,不可变(和编译器有关),所以不可以是变量
!C++源程序编译时,为了保证编译和运行的效率,C++编译系统不对数组下标进行越界检查,程序运行时系统也不会提出越界警告,所以小心,以免破坏其他存储单元的数据
一些初始化的特殊情况
初始值的个数可以比数组元素个数少
int a[2][3]={2,3,4};
当提供全部初始值时,一维的长度可以省略
float grade[]={90.0,75.0,85.0}
int a[][3]={{2,4,6},{8,10,12}}
指针
任何变量都有一个内存地址,该地址称为指针,而指针变量是一种存放内存地址的变量
int* pointer;
int a=10;
int* pa=&a;
- 指针与数组
指针代替下标引用数组元素,使数组的使用更加灵活有效,
数组名表示数组在内存中的首地址(见数据类型问题)
- 程序循环中的pa可以换成a吗?
不可以,数组名就是常指针,而常指针不可变
int a[]={0};
int* pa=a;//a就是&a[0]
......
result+=pa*;
pa++;
字符串
C++语言没有提供字符串类类型,字符串变量作为以为字符串数组处理
C++编译程序自动在字符串的末尾加上字符’\0‘(字符串结束符),求字符串长度时不能将它计算在内
转义字符”\““表示双引号
两种初始化写法,比较不同
char s[]={"hello"};
char s[]="hello";//6个元素
char *ps = “hello”;
const char *ps ="hello";//C++ 11标准
上面的常量是不占系统内存,拷贝一份到数组,但下面那个有指针指向它,占内存空间,只要有指针不被释放,就不能删内存空间。但是两者内存空间想用
const
必须初始化
const int size2 =20;
#define size1 20
- 符号常量和const常量一样吗?该选哪个?
推荐第一种。宏定义在预编译时作文本替换,不做类型检查,而const在编译时会进行类型检查
- 常指针和常值变量指针
double* const p1=&x;//常指针,地址不能变
const double *p2 =&x;//常值变量指针,值不能变
普通指针不能指向常值变量,常值变量不能指向普通指针
结构体
结构属于构造类型,是由多种类型的数据组成
结构中的每个数据项位成员
//定义结构体
struct staffer{
int ID;
char name[20];
bool sex;
float salary;
}
例子:两种访问形式
void eg2_11_0{
staffer employee1={110,"liming",1,23678.39};
staffer employee2= employee1;
employee2.ID=234543;
staffer* pstaff = &employee2;
pstaff -> salary =1000.00;
}
数组名name即&name[0]是个常值变量,所以不能写成employee2.name= “WangPing”;
要写成拷贝字符串 strcpy(employee2.name,“Wangping”)
枚举
允许用符号常量代表数据
enum Color {Red,Green,Blue};//Red对应0,Green对应1,Blue对应2,这是默认的
Color color1 =Red;
cout << color1 << endl;
enum Weather {windy=2,rainy=-1,cloudy,sunny=3};//cloudy的常量是-1+1
- 枚举类型有什么用?为什么不直接用枚举常量对应的数值?
增加程序的可读性
typedef
对原有的数据类型定义一个新的名称
typedef int INT32;
INT32 i=1;//就是int i=1
typedef struct tagDate{
int year;
int month;
int day;
} DATE;
INT32 i = 0;
DATE today = {2025,4,1}
- 使用typedef类型定义有什么用?
增加程序的可移植性(不用全局改,只用改一句就行),可读性,用户自定义
控制语句
if...else if ....else....
switch{
case 'A':
cout << "优秀";
}
注意加break!
switch语句单入口多出库,一通百通
如果没有break,几个case会一直执行下去
- for语句执行顺序?
for(<表达式1>;<表达式2>;<表达式3>){
<语句>
}
先对表达式1求值,然后对表达式2求值,如果表达式2为真,执行循环体,执行完循环体,对表达式3求值,完成一次循环,再对表达式2求值,决定是否进行下一次循环
do-while语句
至少循环一次,先循环再判断
- 三种循环语句比较?
for:循环次数条件明确
while:循环条件明确
do…while….至少循环一次
函数声明
C++允许函数调用在前,定义在后
- 此时要求在调用前必须进行函数声明,把函数名,函数类型,形参告诉编译系统,以便调用时进行语法检查,函数声明放*.h,函数定义放 *.cpp
函数调用过程:中断当前函数的执行,将程序的执行流程转移到被调用函数,并将输参传递给形参,调用结束后返回主调函数
例子:地址,引用
void swap1(int* x,int* y){
int temp = *x;
*x =*y;
*y=temp;
}
void swap2(int& x,int& y){
int temp =x;
x=y;
y=temp;
}
swap1(&a,&b);
swap2(a,b);
内联函数
调用函数时,系统要进行现场处理工作,需要占用附加的现场处理时间
- 把函数体直接嵌入函数调用处,则可消除附加的现场处理的事件开销,提高程序的运行效率
- 调用内联函数时不发生控制转移,知识在编译时把函数体嵌入到调用出
内联函数的定义:
- 在函数头前加入关键字inline
- 当编译程序遇到内联函数调用语句时,会将内联函数的函数体替换调用语句
优缺点
- 加快代码调用速度
- 增加内存的空间开销
函数默认参数
-
有多个默认参数,应该放在参数表的右侧
-
调用时,若省略某实参,则该实参右边的所有实参都必须省略
-
若省略实参,则默认值传递给形参
引用
另一个变量的别名
- 声明引用时,必须对其进行初始化
- 编译器一般将引用实现为const指针,即指向位置不可变的指针,本质是同一个白能量并且占用相同的内存单元,只是名字不一样
int i=10;
int& r=i;
r和i占用的是同一个内存空间,只是两个不同名字
引用一般作为函数参数,能让代码更好看
编译预处理指令
- include文件包含
- define宏定义:不进行类型检查,注意宏的安全,带括号
#define ((a)>(b)?(a):(b))
- 条件编译
决定哪些源程序段将被编译,哪些源程序段将不被编译
#if……#else……#endif……
主要作用:管理测试与生产代码
作用域和生存期区别和关系
- 作用域是变量在源程序中的一段静态区域
- 变量的生存期是指从创建到撤销(分配内存空间到释放)
- 有些变量没有生存期,但有作用域,有时变量虽然在生存期,但不在作用域
变量的内存分配方式
自动分配(运行),静态分配(编译时),动态分配(运行时)
变量的存储类型
auto:内部变量,存储在栈上
register:内部变量,存储在寄存器中
extern:外部变量
static:内部或外部变量
未指定存储类型,内部默认为auto,外部默认为extern
作用域
例:全局变量
extern int b
引用外部变量,不开辟内存空间
例:静态变量
并不会因为函数调用结束而释放内存
比较以下两种:
void fun(){
int a=0;
a++;
cout << "a=" << a << endl;
}
a=1,a=1
void fun(){
static in a=0;
a++;
cout << "a=" << a << endl;
}
a=1,a=2
void exp(){
for(int i=0;i<2;i++){
fun();
}
}
作用域限定符::
int amout=123;//全局变量
void sg2_33(){
int amount =456;//局部变量
cout<<::amout<<endl;//123
cout<<amout<<endl;//456
}
命名空间
命名空间可避免全局标识符同名引起冲突,是对一些成员进行声明的一个描述性区域
引入命名空间后,标准C++库都定义在std中(using namespace std)
函数的存储类型
内部函数static:只能被以同一个源文件中的函数调用
外部函数extern:可以被其他源文件中的函数调用
动态分配内存
动态分配是指在程序运行时为程序中的变量分配内存空间,它完全由应用程序自己进行内存的分配和释放,在堆上分配
(一般来说,编译器在编译阶段就自动将管理这些空间的代码加入到目标文件中,程序运行后由系统自动为变量分配内存空间,在作用域结束后自动释放内存空间)
内存的动态分配与释放
C语言中,动态内存分配时通过调用标准库malloc()和free()实现
C++中,利用new和delete进行动态内存的分配释放
<指针变量> = new <数据类型>;
delete <指针变量>;
delete[]<指针变量>;//释放动态数组
例子:编程输出斐波那契数列
void eg2_36(){
int *f,n;
cin >> n;
f=new int[n+1];
if(n==nullptr||n<1){
cout << "Heap error"
return;
}
f[0]=f[1]=1;
cout << f[0] << f[1] <<endl;
for(int i=2;i<=n;i++){
f[i]=f[i-1]+f[i-2];
cout << f[i] <<endl;
}
delete []f;
}
构造函数和析构函数
- 如何初始化成员变量?
成员变量一般都为私有属性,也不能在声明对象后利用赋值运算对成员变量进行初始化,成员变量的初始化一般时利用构造函数来完成
- 构造函数:创建对象是系统自动调用的成员函数
- 析构函数:对象生存期结束时系统自动调用的成员函数
重载
函数重载,运算符重载,函数名相同函数参数的类型不同
例子:构造函数重载
class Box{
public:
Box();
Box(double h);
Box(double h,double w);
Box(double h,double w,double d);
}
//超级构造函数
Box(double h=0,doubke w =0,double d=0);
重载规则
-
以下非法
-
返回类型不能不同
long fun(int);
float fun(int);
- 不能利用引用重载
void fun(int&)
void fun(int)
- const可用于重载
void fun();
void fun() const;
运算符重载
- 输出流运算符函数原型
ostream& operator«(ostream& os,<操作对象>)
- 加法运算符函数原型
<返回类型> operator+(<操作对象>,<操作对象>)
- 自增运算符函数类型
<返回类型> operator++(<操作对象>)
- 下标运算符函数类型
<返回类型>& operator[<操作对象>,int i]
例子:加法运算符重载,复数相加(普通函数)
class CComplex{
private:
double r,i;
public:
CComplex(double r=0,double i=0);
virtual ~CComplex();
friend CComplex operator+(CComplex c1,CComplex c2);
};
CComplex operator+(CComplex c1,CComplex c2){
CComplex Ctemp;
CTemp.r = c1.r+c2.r;
CTemp.i = c1.i+c2.e;
return CTemp;
}
例子:自增运算符重载
class Counter{
private:
int value;
public:
Counter(){ value = 0;}
Counter operator++();//前缀运算符
Counter operator++(int);//后缀运算符
void display(){
cout << "value:"<<value << endl;
}
};
Counter Counter::operator++(){
value++;
return *this;
}
Counter Counter::operator++(int){
Counter temp;
temp.value=this->value++;
return temp;
}
写成++i前缀运算符效率更高,不用创建临时变量
模板
函数模板
编译器根据函数实参的数据类型确定模板参数T,再自动生成对应的函数,即模板函数
template <class T>;
T my_abs(T val){
return (val<0)?-val : val;
}
void eg4_22(){
int i=100;
long l=-123456L;
float f=-12.78F;
cout<<my_abs(i)<< my_abs(l)<<my_abs(f)<<endl;
}
类模板
//内部定义成员函数
template <typename T>
class MyTemClass{
private:
T x;
public:
void setX(T a){ x=a;}
void getX(){ return x;}
}
//外部定义成员函数
template <typename T1>
void MyTemClass<T1>::setX(T1 x)
{
this->x=x;
}
template <typename T>
void MyTemClass<T>::getX(){
return this->x;
}
含多个参数的类模板
template <typename T1,int i,typename T2>
class MyClassM{};
//实例化
MyclassM<int,100,float> MyObject;