Frederick

Welcome to my Alter Ego's site!

Mar 30, 2025 - 13 minute read - Comments

一些对于C++的思考

  • 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 vertices;<>内可以是int,与java不同,C++可以传递数据类型

  • 为什么存储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;

常用板子 OS

comments powered by Disqus