C++基础知识总结

如果一个世界都能在弹指一挥间灰飞烟灭,一个人的终结也就应该如露珠滚下草叶般平静淡然。

常量指针const int *a和指针常量 int * const a 区别

const int a;
int const a;
const int *a;
int *const a;

1.int const a和const int a均表示定义常量类型a。
2.常量指针是一个指针,读成常量的指针,指向一个只读变量。如int const *pconst int *p
3.指针常量是一个不能给改变指向的指针。如int *const p

int main()
{
  int b = 3;
  int c = 4;
  const int *p = &b;  //等价于 int const *p = &b;
  p = &c;        //修饰值,指针可变
              //*p = 5;//error 修饰值,值不可变
  cout << *p << endl;
  int a = 5;
  int * const q = &a;   //修饰指针
              //p = &c;//error修饰指针,指针不可变
  *p = 5;        //修饰指针,值可变
}

sizeof 和 strlen 的区别

1.sizeof 是一个操作符,strlen 是库函数。
2.sizeof 的参数可以是数据的类型,也可以是变量,而 strlen 只能以结尾为‘\ 0‘的字符串作参数。
3.编译器在编译时就计算出了sizeof 的结果。而strlen 函数必须在运行时才能计算出来。并且 sizeof计算的是分配时数据类型占内存的大小,而 strlen 计算的是字符串实际的长度。
4.数组做 sizeof 的参数不退化,传递给 strlen 就退化为指针了。
5.sizeof 操作符的结果类型是 size_t,它在头文件中 typedef 为 unsigned int 类型。该类型保证能容纳实现所建立的最大对象的字节大小。
6.当适用一个结构类型或变量时, sizeof 返回实际的大小;当适用一静态地空间数组, sizeof 归还全部数组的尺寸;sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸。

C中的 malloc 和C++中的 new 有什么区别

1.new、delete 是操作符,可以重载,只能在 C++中使用。
2.malloc、free 是函数,可以覆盖,C、C++中都可以使用。
3.new 可以调用对象的构造函数,对应的 delete 调用相应的析构函数。
4.malloc 仅仅分配内存,free 仅仅回收内存,并不执行构造和析构函数
5.分配成功时:new、delete 返回的是某种数据类型指针,malloc、free 返回的是 void 指针。
6.分配失败时:malloc返回NULL,new默认抛出异常。

注意:malloc 申请的内存空间要用 free 释放,而 new 申请的内存空间要用 delete 释放,不要混用。因为两者实现的机理不同。

c/c++中static

要理解static,就必须要先理解另一个与之相对的关键字auto,其实我们通常声明的不用static修饰的变量,都是auto的,因为它是默认的。auto的含义是由程序自动控制变量的生存周期,通常指的就是变量在进入其作用域的时候被分配,离开其作用域的时候被释放;而static就是不auto,变量在程序初始化时被分配,直到程序退出前才被释放;也就是static是按照程序的生命周期来分配释放变量的,而不是变量自己的生命周期;所以,像这样的例子:

void func()
{
  int a;
  static int b;
}

每一次调用该函数,变量a都是新的,因为它是在进入函数体的时候被分配,退出函数体的时候被释放,所以多个线程调用该函数,都会拥有各自独立的变量a,因为它总是要被重新分配的;

而变量b不管你是否使用该函数,在程序初始化时就被分配的了,或者在第一次执行到它的声明的时候分配(不同的编译器可能不同),所以多个线程调用该函数的时候,总是访问同一个变量b,这也是在多线程编程中必须注意的!

1.static可以修饰局部变量(静态局部变量)、全局变量(静态全局变量)和函数,被修饰的变量存储位置在静态区。

  • 对于静态局部变量,相对于一般局部变量其生命周期延长,直到程序运行结束而非函数调用结束,且只在第一次被调用时定义;
  • 对于静态全局变量,相对于全局变量其可见范围被缩小,只能在本文件中可见;
  • 修饰函数时作用和修饰全局变量相同,都是为了限定访问域。

2.C++的static除了上述两种用途,还可以修饰类成员(静态成员变量和静态成员函数),静态成员变量和静态成员函数不属于任何一个对象,是所有类实例所共有。

3.static的数据记忆性可以满足函数在不同调用期的通信,也可以满足同一个类的多个实例间的通信。

4.未初始化时,static变量默认值为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0×00,某些时候这一特点可以减少程序员的工作量。

引用和指针区别

1.引用只是别名,不占用具体存储空间,只有声明没有定义;指针是具体变量,需要占用存储空间。
2.引用在声明时必须初始化为另一变量,一旦出现必须为typename refname = &varname形式;
3.指针声明和定义可以分开,可以先只声明指针变量而不初始化,等用到时再指向具体变量。
4.引用一旦初始化之后就不可以再改变(变量可以被引用为多次,但引用只能作为一个变量引用)而指针变量可以重新指向别的变量。
5.不存在指向空值的引用,必须有具体实体;但是存在指向空值的指针。

数组指针

区别以下指针类型

 int *p[10]
 int (*p)[10]
 int *p(int)
 int (*p)(int)

1.int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
2.int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
3.int p(int)是函数声明,函数名是p,参数是int类型的,返回值是int 类型的。
4.int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

a和&a有什么区别?

#include <iostream>
using namespace std;
int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int *ptr = (int*)(&a+1);
    cout << *(ptr-1) << "\t" << *(ptr-2) << endl; // 5 4
    cout << "----------------" << endl;
    int *p = (int *)(a+1);            //2
    cout << *p << endl;
}

1.a是数组名,是数组首元素地址,+1表示地址值加上一个int类型的大小,如果a的值是0x00000001,加1操作后变为0x00000005。*(a + 1) = a[1]
2.&a是数组的指针,其类型为int(*)[5](就是前面提到的数组指针),其加1时,系统会认为是数组首地址加上整个数组的偏移(5个int型变量),值为数组a尾元素后一个元素的地址。
3.若(int )p ,此时输出 *p时,其值为a[0]的值,因为被转为int 类型,解引用时按照int类型大小来读取。

数组名和指针(这里为指向数组首元素的指针)区别?

1.二者均可通过增减偏移量来访问数组中的元素。
2.数组名不是真正意义上的指针,可以理解为常指针,所以数组名没有自增、自减等操作。
3.当数组名当做形参传递给调用函数后,就失去了原有特性,退化成一般指针,多了自增、自减操作,但sizeof运算符不能再得到原数组的大小了。

野指针是什么?

1.也叫空悬指针,不是指向null的指针,是指向垃圾内存的指针。
2.产生原因及解决办法:

  • 指针变量未及时初始化 => 定义指针变量及时初始化,要么置空。
  • 指针free或delete之后没有及时置空 => 释放操作后立即置空。

delete和delete[]区别?

1.delete只会调用一次析构函数。
2.delete[]会调用数组中每个元素的析构函数。
3.char *p = new char[32];如果用delete p,会发生什么?

  • 因为char是基本数据类型没有析构函数,而系统也记忆了内存大小。所以用delete不会造成内存泄露。但是这种方法在C++标准里是未定义行为,要避免使用。
  • 如果用malloc和free怎么申请和释放32个字节的buffer。
    char ptr = (char)malloc(32);free(ptr);ptr = nullptr;
  • 如果用free(p) 可以吗?
    这是未定义行为。但是对于基本数据类型是可以使用的。
  • 为什么delete []就可以删除整个数组。
    因为系统分配额外空间记录数组大小。具体来说是在p指针附近(下个位置)存储了指针指向物体的大小的信息

堆和栈的区别?

1.申请方式不同。

  • 栈由系统自动分配。
  • 堆由程序员手动分配。

2.申请大小限制不同。

  • 栈顶和栈底是之前预设好的,大小固定,可以通过ulimit -a查看,由ulimit -s修改。
  • 堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。

3.申请效率不同。

  • 栈由系统分配,速度快,不会有碎片。
  • 堆由程序员分配,速度慢,且会有碎片。

4.生长方向不同

  • 堆:向着内存地址增加的方向增长
  • 栈:向着内存地址减小的方向增长,由内存的高地址向低地址方向增长

简述C\C++程序编译的内存情况分配

C、C++中内存分配方式可以分为三种:

1.从静态存储区域分配:内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量,static变量等。

2.在栈上分配:在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

3.从堆上分配:即动态内存分配。程序在运行的时候用 malloc 或 new 申请任意大小的内存,程序员自己负责在何时用 free 或 delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。

一个 C、C++程序编译时内存分为 5 大存储区:堆区、栈区、全局区、文字常量区、程序代码区。

变量声明和定义区别

1.声明仅仅是把变量的声明的位置及类型提供给编译器,并不分配内存空间;定义要在定义的地方为其分配存储空间。

2.相同变量可以再多处声明(外部变量extern),但只能在一处定义。

extern关键字

1.extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

2.当它与”C”一起连用时,如:extern “C” void fun(int a, int b); 主要是解决在C++代码中调用C代码。告诉编译器,被extern “C”修饰的变量和函数,按照C语言方式编译和链接。

函数传参有几种方式?传引用和指针有什么区别呢?

1.传值:形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。

2.传指针:形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作

3.传引用:形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。

4.传指针和传引用区别:主要是指针和引用的区别。首先语法就不一样,指针要取值需要能*ptr,引用可以直接取; 另外传进来的指针可以指向其他位置,但是引用只能绑定在传进来的固定的值上。

“零值比较”?

1.bool类型:if(flag)
2.int类型:if(flag == 0)
3.指针类型:if(flag == nullptr)
4.float类型:if((flag >= -0.000001) && (flag <= 0. 000001))

同一类的不同对象可以互相赋值吗?

1.可以,但含有指针成员时需要注意。
2.对比类的对象赋值时深拷贝和浅拷贝。

const的作用

1.修饰普通变量: 该变量值不可更改。顶层/底层const
2.修饰函数参数: 函数形参声明加const保护某些值在操作过程中不会改变
3.修饰返回值:表明返回的数据是不可修改的
4.修饰成员函数: 类的成员函数加上const限定可以声明此函数不会更改类对象的内容

宏定义和函数有何区别?

1.宏在编译时完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快;函数调用在运行时需要跳转到具体调用函数。

2.宏定义没有返回值;函数调用具有返回值。

3.宏定义参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。

宏定义和内联函数(inline)区别?

1.在使用时,宏只做简单字符串替换(编译前)。而内联函数可以进行参数类型检查(编译时),且具有返回值。
2.内联函数本身是函数,强调函数特性,具有重载等功能。
3.内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了。

宏定义和const全局变量的区别?

1.宏替换发生在预编译阶段,属于文本插入替换;const作用发生于编译过程中。
宏不检查类型;const会检查数据类型。
2.宏定义的数据没有分配内存空间,只是插入替换掉;const变量分配内存空间。
3.宏不是语句,不在最后加分号;

宏定义和typedef区别?

1.宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。
2.宏替换发生在编译阶段之前,属于文本插入替换;typedef是编译的一部分。
3.宏不检查类型;typedef会检查数据类型。
4.宏不是语句,不在最后加分号;typedef是语句,要加分号标识结束。
5.注意对指针的操作,typedef (char*) p_char#define p_char char*在使用起来区别巨大。

条件编译#ifdef, #else, #endif作用?

1.可以通过加#define,并通过#ifdef来判断,将某些具体模块包括进要编译的内容。
2.用于子程序前加#define DEBUG用于程序调试。
3.应对硬件的设置(机器类型等)。
4.条件编译功能if也可实现,但条件编译可以减少被编译语句,从而减少目标程序大小。

volatile有什么作用?

1.volatile定义变量的值是易变的,每次用到这个变量的值的时候都要去重新读取这个变量的值,而不是读寄存器内的备份。
2.多线程中被几个任务共享的变量需要定义为volatile类型。

什么是常引用?

1.常引用可以理解为常量指针,形式为const typename & refname = varname
2.常引用下,原变量值不会被别名所修改。
3.原变量的值可以通过原名修改。
4.常引用通常用作只读变量别名或是形参传递。

模板的优点和缺点

优点:
1.代码复用。
2.模板类更加的安全,因其参数类型在编译时都是已知的。
缺点:
1.模板必须在头文件中,这样一旦有所变更需要重编译所有相关工程;同时也没有信息隐藏。
2.一些编译器对template支持不好。
3.模板对每种类型生成额外的代码,可能导致代码膨胀。

模板的特化与偏特化

全特化就是限定死模板实现的具体类型,偏特化就是如果这个模板有多个类型,那么只限定其中的一部分。

posted @ 2021-12-12 17:23  煊奕  阅读(521)  评论(0编辑  收藏  举报