一,指针和数组的区别
1,数组指的是一块连续的内存,存放元素类型都相同,
指针是一个变量,保存了一块内存地址。
2,长度不同,用sizeof计算数组,计算的是这块连续内存的长度。
3,赋值方式,数组和数组之间不能直接赋值,指针可以直接赋值。
4,指针可修改,数组不行,例如
str:指针变量; ch[16]:数组
str++ 正确; ch++ 错误;
二,const关键字的作用
1,const修饰变量,此变量变为不可变的量
【例】:const int bufsize = 512; //不可修改
2,创建const常量必须初始化。
3,const只在本文件有效,除非用extern
4,const和引用:
常量必须用常引用,
【例】:const int ci = 1024;
const int &r2 = ci;
5,const和指针:
常量指针必须被初始化,
【例1】:int i = 10;
const int* pt = &i;
const int *pt 与 int const *pt 相同。
可以改变pt的指向,也就是pt可以指向其他变量;
不能改变和修改指针指向的对象。
【例2】:int i = 10;
int * const pt = &i;
不可以改变pt的指向;
可以改变pt指向的对象。
【总结】:const在 * 前面时,修饰的是指针指向的对象,
const在 * 后面时,修饰的是指针变量。
6,const修饰的函数为常函数,常函数不可修改成员属性。
【例】 class Person
{
public:
void ShowPerson() const
{
m_age = 12;
}
public:
int m_age = 10;
};
报错,m_age不可修改。
m_age加上mutable关键字之后,可修改
【例】 class Person
{
public:
void ShowPerson() const
{
m_age = 12;
}
public:
mutable int m_age = 10;
};
7,常对象,
const Person per;
常对象只能调用常函数。
三, static关键字:
1,c语言中的关键字:
(1)修饰局部变量,该变量变为静态局部变量存储在静态存储区,
未经初始化的静态变量会被自动初始化为0;
作用域:作用域仍为局部作用域,但生命周期变长,当静态变量离开作用域
后并没有被销毁。只不过我们不能再作用域外进行访问。
(2)修饰全局变量,变为全局静态变量。
未经初始化的静态全局变量会被初始化为0;
作用域:只能在本文件中。即使用extern也无法在其他文件使用
如果不是静态全局变量而是普通全局变量,用extern即可在其他文件中使用。
(3)静态函数,
在函数前加上static就变成了静态函数,和全局静态变量一样,只能在本文件中
使用。
2,c++中的static关键字,也就是面向对象中的static关键字。
类中的static成员数据是同一个类下的多个对象共享的,存储在静态区,
静态成员数据类内定义类外初始化。
类中静态成员函数只能引用类的静态成员变量,不能引用类的非静态成员变量。
四, 四种类型转换操作符,static_cast,const_cast,reinterpret_cast,dynamic_cast。
1,static_cast,
(1)基础数据类型之间的转换(如int, float, char等)之间的转换
int i = 42;
double d = static_cast<double>(i); //将int转换为double
(2) 类的层级结构中,基类和子类之间指针或者引用的转换。
上行转换(Upcasting),也即子类像基类方向转换,是安全的。
下行转换(Downcasting),也即基类向子类方向转换,是不安全的,
上行转换:
下行转换:
void*到其他类型的转换:
如果有一个void*
指针,你可以使用static_cast
将其转换为其他类型的指针。但这 种转换不会进行任何运行时类型检查,因此使用时必须小心。
void* voidPtr = /* ... */;
int* intPtr = static_cast<int*>(voidPtr); // 将void*转换为int*
2,const_cast 用来去掉表达式的 const 修饰或 volatile 修饰,也就是将 const 或 volatile 类型转换为非 const 或 非 volatile 类型。
const int n = 111;
int *p = const_cast<int*>(&n);
*p = 222;
3,dynamic_cast<type-id>(expression)
把 expression 转换为 type-id 类型,type-id 必须是类的指针、类的引用或者是
void *;如果 type-id 是指针类型,那么 expression 也必须是一个指针;如果
type-id 是一个引用,那么 expression 也必须是一个引用。
dynamic_cast 提供了运行时的检查。对于指针类型,在运行时会检查 expression 是否真正的指向一个 type-id 类型的对象,如果是,则能进行正确的转 换;否则返回 nullptr。对于引用类型,若是无效转换,则在运行时会抛出异常 std::bad_cast。
其实 dynamic_cast 本质只支持上行转换,只会沿着继承链向上遍历,找到目标类型则 转换成功,否则失败。dynamic_cast 看似支持下行转换,这都是多态的缘故。上面的 例子,pa1 虽然类型是 A,但实际指向 B,沿着 B 向上可以找到 B,因为第一个转换可 以成功。而 pa2 指向 A,沿着 A 向上找不到 B 类型,因而转换失败。
因而在有继承关系的类的转换时候, static_cast 转换总是成功的, dynamic_cast 显然 比 static_cast 更加安全。
五,指针和引用的区别:
- 初始化:引用在定义时必须初始化,指针则没有要求(尽量初始化,防止野指针)
- 引用在初始化引用一个实体后,就不能再引用其它实体,而指针可以在任意时候指向一个同 类型实体
- 没有NULL引用,但是有nullptr指针
- 在sizeof中含义不同: 引用结果为引用类型的大小,但指针始终是地址空间,所占字节个数(32位平台占4个字节)
- 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但没有多级引用
- 访问实体的方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对安全
六, new/delete malloc/free
1,malloc/free是C/C++标准库函数,而new/felete是C++中的操作符,
malloc/free需要头文件支持,new/delete需要编译器支持。
2,malloc申请内存时,需要我们指定申请空间大小,且返回的类型为void*,需要将其强制 转换为所需要类型指针;
new申请内存时,会根据所申请的类型自动计算申请空间大小,且可以返回指定类型指针
int* array = (int*)malloc(10 * sizeof(int));
int* ar = new int(10);
3,new在申请堆区空间的同时,还可对数据进行初始化,malloc不能进行初始化。
malloc开辟内存失败会返回nullptr,new失败则会抛出bad_alloc异常。
4,malloc/free申请释放内存时,不需要调用构造/析构函数,
new/delete申请释放内存时需要调用构造/析构函数。
七,explicit函数
上图中,MyString str3 = "abcd"; 和 MyString str1 = 1; 都属于隐式转换。
explicit就杜绝了这种隐式转换。
八,内联函数,在函数前加 inline 关键字。
内联函数的意思是:将项目中某些常用的简单逻辑的函数申请为内联函数,
如果编译器认定可以设置为内联函数,在编译之前,就会将主调函数中调用该内联函数的位置
直接替换为该函数体的内容,在进行编译,这样就省去了运行时,调用CPU开销
大大的提升程序的执行效率。
一定是简单的逻辑结构,不能包含循环结构等复杂结构。
编译特性:即使开发者将函数声明成了内联函数,编译器也并不一定会将该函数当做内联函 数来编译。
符合内联函数的特点的函数:要求在程序中会被大量调用,函数体内部,没有分支循环,函数 体内部语句不超过五行,函数不是递归形式的。
九,strcpy,sprintf,memcpy,memset,scanf;
1,sprintf
sprintf指的是字符串格式化命令,是把格式化得数据写入某个字符串中,即发送格式化输出 到string所指向的字符串,直到出现字符串结束符‘\0’为止。
2,strcpy
strcpy,即stringcopy(字符串复制)的缩写,strcpy是C++语言的一个标准函数, strcpy把含有’\0’结束符的字符串复制到另一个地址空间dest,返回值的类型为char *。
2、声明
char *strcpy(char *dest , const char *src);
3、参数列表
(1)char *dest:指向用于存储复制内容的目标数组;
(2)const char *src:需要复制的字符串;
3,memcpy
1、定义
memcpy指的是C和C++使用的内存拷贝函数,功能是从源内存地址的起始位置开始拷贝 若干个字节到目标内存地址中,即从源source中拷贝n个字节到目标destin中。
2、声明
void *memcpy(void *destin , void *source , unsigned n);
3、参数列表
(1)void *destin:指向用于存储复制内容的目标数组,类型强制转换为void *指针;
(2)void *source:指向要复制的数据源,类型强制转换为void *指针;
(3)unsigned n:要被复制的字节数;
4、返回值
该函数返回一个指向目标存储区destin的指针。
4,memset
1、定义
memset是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为 指定的值,这个函数通常为新申请的内存做初始化工作。
2、声明
void *memset(void *str , int ch , int counter ) ;//将str后面的counter个字节用ch替 换并返回str
3、参数列表
(1)void *str:str为指针或者是数组;
(2)int ch:ch是赋给str的值;
(3)int counter:counter是str的长度;
5、strcmp
1、定义
strcmp函数是string compare(字符串比较)的缩写,用于比较两个字符串并根据比较结 果返回整数,基本形式为strcmp(str1 , str2),若str1=sstr2,则返回为零;若 str1<str2,则返回为负数;若str1>str2,则返回为正数。
2、声明
int strcmp(const char *str1 , const char *str2) ;
3、说明
(1)当str1<str2时,返回为负数;
(2)当str1=str2时,返回值为0;
(3)当str1>str2时,返回值为正数;
十,类和结构体的区别
在c语言时代,结构体并没有构造函数和成员函数。
C++中结构体可以拥有构造函数、析构函数、成员函数等。
1,默认访问控制符区别,
结构体默认public访问,类默认private访问。
结构体一般都用来表示简单复合数据类型。
类有继承,多态,结构体没有。
十一,override
在C++中,override
是一个关键字,用于明确指出派生类中的成员函数重写了基类中的虚函数。它提供了类型安全的检查,确保派生类中的函数确实重写了基类中的虚函数,并且具有相同的签名。
当在派生类中使用override
关键字时,编译器会检查该函数是否与基类中的虚函数具有相同的函数签名(包括返回类型、函数名以及参数列表)。如果签名不匹配,编译器会发出错误信息,从而防止因误写或理解错误而导致的潜在问题。
十二,# ifdef #ifndef 等用法
预处理就是在进行编译的第一遍词法扫描和语法分析之前所作的工作。说白了,就是对源文件进行编译前,先对预处理部分进行处理,然后对处理后的代码进行编译。这样做的好处是,经过处理后的代码,将会变的很精短。
关于预处理命令中的文件包含(#i nclude),宏定义(#define),书上已经有了详细的说明,在这里就不详述了。这里主要是对条件编译(#ifdef,#else,#endif,#if等)进行说明。以下分3种情况:
1:情况1:
#ifdef _XXXX
...程序段1...
#else
...程序段2...
#endif
这表明如果标识符_XXXX已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。
例:
#define NUM
.............
.............
.............
#ifdef NUM
printf("之前NUM有过定义啦!:) \n");
#else
printf("之前NUM没有过定义!:( \n");
#endif
}
如果程序开头有#define NUM这行,即NUM有定义,碰到下面#ifdef NUM的时候,当然执行第一个printf。否则第二个printf将被执行。
我认为,用这种,可以很方便的开启/关闭整个程序的某项特定功能。
文件中的#ifndef
头件的中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。
还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:
#ifndef <标识>
#define <标识>
......
......
#endif
<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,如:stdio.h
#ifndef _STDIO_H_
#define _STDIO_H_
......
#endif
十二,const引用
const引用是const变量的别名,由于const变量的值不能随意修改,修改普通引用会导致原
来的值改动,所以需要const引用来修饰const变量,非const引用不能修改原来的变量。
十三,c++11新特性
auto关键字:
auto关键字的作用是推倒出一个变量初始化表达式中变量的类型。
auto类型推断发生在编译时期,所以出现无法推断的情况下编译器会报错。
没有初始化的变量是不可以用auto的,最典型的例子就是函数的参数不可以是auto
decltype关键字:
decltype与auto相反,不需要给变量进行初始化,而是将别的变量的类型推导出来
拷贝给当前变量。
区间迭代:
区间迭代通常与范围基础的for循环相结合,用于遍历容器(如vector、list等)中的元素
适用于便利数组,顺序容器和关联容器
初始化列表:
Lambda表达式:
也叫匿名函数,特点在于匿名函数只用一次。
final关键字:
1:可以阻止类继承出派生类,如下图,被finl修饰的Base类,A类就不能继承Base类了。
2:可以阻止一个虚函数在派生类中被重新实现
如下图,move()已经被final修饰,不能被重写了。
十四,与或非
逻辑与运算:&
语法:全1为1 有0为0
特点:和1相与保持不变,和0相与为0
场景:指定位清0
逻辑或运算:
语法:有1为1 全0为0
特点:和1或置1,和0或保持不变
场景:将指定位置 置1
十五,const和define的区别
(1)就起作用的阶段而言: #define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
(2)就起作用的方式而言: #define只是简单的字符串替换,没有类型检查。
而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
(3)就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,
它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份。
(4)从代码调试的方便程度而言: const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了。
十六,什么是抽象类
抽象类: 包含纯虚函数的类是抽象类
特点:不能单独的创建抽象类对象
子类必须重写纯虚函数
十七,c++内存分区有哪几块
五块
- 栈区(stack)
— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
- 堆区(heap)
— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
- 全局区(静态区)(static)
—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
- 文字常量区
—常量字符串就是放在这里的。 程序结束后由系统释放
- 程序代码区
—存放函数体的二进制代码。
十八,野指针
野指针指向了一块随机内存空间,不受程序控制。如1、未初始化的指针,指针指向一个已删 除的对象或者指向一块没有访问权限的内存空间;2、free(p)后,p没有置null,。注:指针
释放需置NULL。
十九,请你来说一说 extern “C”
C++调用 C 函数需要 extern C,因为 C 语言没有函数重载
二十,请你来回答一下++i和i++的区别
++i先自增1,再返回,i++先返回i,再自增1
二十一,include双引号(" ")和尖括号<>的区别
尖括号是先在系统目录下进行查找
#include<>格式:引用标准库头文件,编译器从标准库目录开始搜索
尖括号<> 编译时会直接在include文件查找文件进行编译,如果找不到就会报错
双引号是首先在当前目录下进行查找
#include" "格式:引用非标准库头文件,编译器从用户的当前工作目录下开始搜索
双引号 “ ”编译时首先查找用户文件所在目录,查找不到的情况下,再在include安装目录下继续查找
原则上来讲,使用双引号 “ ”一定不会编译出错,但是按照标准规定,标准库应该用尖括号<>