文章目录
0.指针
link
指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name;
在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:
int *ip; /* 一个整型的指针 */
double *dp; /* 一个 double 型的指针 */
float *fp; /* 一个浮点型的指针 */
char *ch; /* 一个字符型的指针 */
实际用法
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 在指针变量中存储 var 的地址
cout << "Value of var variable: ";
cout << var << endl;
// 输出在指针变量中存储的地址
cout << "Address stored in ip variable: ";
cout << ip << endl;
// 访问指针中地址的值
cout << "Value of *ip variable: ";
cout << *ip << endl;
return 0;
区别传递指针给函数以及传递引用给函数
C++ 允许您传递指针给函数,只需要简单地声明函数参数为指针类型即可。
#include <iostream>
#include <ctime>
using namespace std;
// 在写函数时应习惯性的先声明函数,然后在定义函数
void getSeconds(unsigned long *par);
int main ()
{
unsigned long sec;
getSeconds( &sec );
// 输出实际值
cout << "Number of seconds :" << sec << endl;
return 0;
}
void getSeconds(unsigned long *par)
{
// 获取当前的秒数
*par = time( NULL );
return;
}
形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。
被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
引用和指针区别:
指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。 而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)
1.字符数组
-
比如char a[10],char和[10]搭配,意思是10个字符长的字符数组,a就是一个这样的字符数组。
-
那么char a[10]呢?char是字符指针,char*和[10]搭在一起,意思是10个字符指针长的字符指针数组,a就是这样一个字符指针数组。
-
而char(*a)[10]呢?因为有括号,*是和a结合在一起,而char是和[10]在一起,则是10个字符长的字符数组,*a是这样的一个字符数组,也就是说a是指向这样一个字符数组的指针。
-
typedef char T[10]呢?char和[10]搭在一起,意思是10个字符长的一个数组,typedef定义了T是char[10]这样的一种数据类型。
T * a;*应该和a搭在一起,所以a就是指向T这样一种数据类型的指针,所以等价于char (*a)[10]
2. C++ 虚函数和纯虚函数的区别
定义一个函数为虚函数,不代表函数为不被实现的函数。
定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
定义一个函数为纯虚函数,才代表函数没有被实现。
定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
2.1 纯虚函数
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加 =0:
virtual void funtion1()=0
、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。
3.函数模板&类模板
4.Qt面试题
2.25 指针和引用区别
-
1、两者的定义和性质不同
指针是一个变量,存储的是一个地址,指向内存的一个存储单元;
引用是原变量的一个别名,跟原来的变量实质上是同一个东西。
而引用 r,是 a 的一个别名,在内存中 r 和 a 占有同一个存储单元。 -
2、指针可以有多级,引用只能是一级
-
3、指针可以在定义的时候不初始化,引用必须在定义的时候初始化
-
4、指针可以指向NULL,引用不可以为NULL
-
5、指针初始化之后可以再改变,引用不可以
-
6、sizeof 的运算结果不同
int a = 996;
int p = &a;
int &r = a;
cout << sizeof§; // 返回 int 类型的大小
cout << sizeof®; // 返回 int 类型的大小
在64位机器上,int* 类型的大小为8个字节,int类型的大小为4个字节。
2.26 C++基础知识
2.26.1 sizeof字符数组
char a[5] = {‘h’, ‘e’, ‘l’ , ‘l’, ‘o’};
那么sizeof(a) = 5;
如果char a[] = “hello”;//或者char a[6] = “hello”
那么sizeof(a) = 6;//加上字符串中的最后一个"\0"
sizeof()是获得字符数组在内存中所占的字节数,你的说法也有点说的过去吧.反正这两个数字大小一样.
要获得字符数组的实际长度(不包括\0),就用strlen();
2.26.2 c=a+++b
inta=5,b=7,c;c=a+++b; c=(a++)+b=12;
2.26.3 在C++程序中调用被C编译器编译后的函数,为什么要加extern"C"
C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为: void foo(int x, int y);该函数被C编译器编译后在库中的名字为foo,而C++编译器则会产生像foointint之类的名字。C++提供了C连接交换指定符号extern"C"来解决名字匹配问题。
参考链接link
参考链接2link
2.26.4 栈和堆的区别
1、栈由系统自动分配,而堆是人为申请开辟;2、栈获得的空间较小,而堆获得的空间较大;3、栈由系统自动分配,速度较快,而堆一般速度比较慢;4、栈是连续的空间,而堆是不连续的空间。”
2.27.5 什么是预编译:
预编译又称为预处理 , 是做些代码文本的替换工作。
处理以# 开头的指令 , 比如拷贝 #include 包含的文件代码,#define 宏定义的替换 , 条件编译等,就是为编译做的预备工作的阶段。
2.27.6 char * const p char const * p const char *p 上述三个有什么区别?
char * const p; //常量指针,p的值不可以修改
char const * p;//指向常量的指针,指向的常量值不可以改
const char *p; //和char const *p
参考
- const和define区别
(1)就起作用的阶段而言: #define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
(2)就起作用的方式而言: #define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
(3)就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份。
(4)从代码调试的方便程度而言: const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了。
2.27.7 计算机内部如何存储负数和浮点数?
负数比较容易,就是通过一个标志位和补码来表示。
【计算机中所有数都是以补码形式存储的】
对于浮点类型的数据采用单精度类型(float)和双精度类型(double)来存储,float数据占用32bit,double数据占用64bit,我们在声明一个变量float f= 2.25f的时候,是如何分配内存的呢?如果胡乱分配,那世界岂不是乱套了么,其实不论是float还是double在存储方式上都是遵从IEEE的规范的,float遵从的是IEEE R32.24 ,而double 遵从的是R64.53。更多可以参考浮点数表示。
无论是单精度还是双精度在存储中都分为三个部分:
1). 符号位(Sign) : 0代表正,1代表为负
2). 指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储
3). 尾数部分(Mantissa):尾数部分
其中float的存储方式如下图所示:
而双精度的存储方式如下图:
2.27.8 static 有什么用途
1). 静态(局部/全局)变量
2). 静态函数
3). 类的静态数据成员
4). 类的静态成员函数
参考链接
参考链接2
2.27.9 类的静态成员变量和静态成员函数各有哪些特性?
静态成员变量
1). 静态成员变量需要在类内声明(加static),在类外初始化(不能加static),如下例所示;
2). 静态成员变量在类外单独分配存储空间,位于全局数据区,因此静态成员变量的生命周期不依赖于类的某个对象,而是所有类的对象共享静态成员变量;
3). 可以通过对象名直接访问公有静态成员变量;
4). 可以通过类名直接调用公有静态成员变量,即不需要通过对象,这一点是普通成员变量所不具备的。
class example{
public:
static int m_int; //static成员变量
};
int example::m_int = 0; //没有static
cout<<example::m_int; //可以直接通过类名调用静态成员变量
1
2
3
4
5
6
7
8
静态成员函数
1). 静态成员函数是类所共享的;
2). 静态成员函数可以访问静态成员变量,但是不能直接访问普通成员变量(需要通过对象来访问);需要注意的是普通成员函数既可以访问普通成员变量,也可以访问静态成员变量;
3). 可以通过对象名直接访问公有静态成员函数;
4). 可以通过类名直接调用公有静态成员函数,即不需要通过对象,这一点是普通成员函数所不具备的。
class example{
private:
static int m_int_s; //static成员变量
int m_int;
static int getI() //静态成员函数在普通成员函数前加static即可
{
return m_int_s; //如果返回m_int则报错,但是可以return d.m_int是合法的
}
};
cout<<example::getI(); //可以直接通过类名调用静态成员变量
2.27.10 构造函数和析构函数的执行顺序?
构造函数
1). 首先调用父类的构造函数;
2). 调用成员变量的构造函数;
3). 调用类自身的构造函数
2.27.11 构造函数可否为虚函数
构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。不建议在构造函数和析构函数里面调用虚函数。构造函数不能声明为虚函数的原因是:1 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功…”
2.28 内存的分配方式的分配方式有几种?
【参考答案】:
1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。
2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
【new-delete-malloc-free】
- new/delete会调用对象的构造/析构函数, 而malloc/free只会释放内存;
- malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符;
- 它们都可用于申请动态内存和释放内存;
- 对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数;
- new操作符申请内存分配时无须指定内存块的大小, 编译器会根据类型信息自行计算;
而malloc则需要显式地指出所需内存的尺寸
30.条件编译
三、条件编译中使用的预编译指令#
#define 定义一个预处理宏
#undef 取消宏的定义
#if 编译预处理中的条件命令,相当于C语法中的if语句
#ifdef 判断某个宏是否被定义,若已定义,执行随后的语句
#ifndef 与#ifdef相反,判断某个宏是否未被定义
#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足,则执行#elif之后的语句,相当于C语法中的else-if
#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足,则执行#else之后的语句,相当于C语法中的else
#endif #if, #ifdef, #ifndef这些条件命令的结束标志.
defined 与#if, #elif配合使用,判断某个宏是否被定义
#if 条件语句
程序段1 //如果条件语句成立,那么就编译程序段1
#endif
程序段2//如果条件不语句成立,那么就编译程序段2
#ifndef x//先测试x是否被宏定义过
#define 程序段1 //如果x没有被宏定义过,那么就编译程序段1
#endif
程序段2 //如果x已经定义过了则编译程序段2的语句,“忽视”程序段1。
31.构造函数
31.1 浅拷贝” 、“深拷贝
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
32 指针数组和数组指针
- 指针数组:指针数组可以说成是”指针的数组”,首先这个变量是一个数组,其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型,在32位系统中,指针占四个字节。
- 数组指针:数组指针可以说成是”数组的指针”,首先这个变量是一个指针,其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。
根据上面的解释,可以了解到指针数组和数组指针的区别,因为二者根本就是种类型的变量。
33 运算符重载
- 函数名是由关键字 operator 和其后要重载的运算符符号构成的。
- 若定义为非成员函数:
Box operator+(const Box&,const Box&)
- 若定义为成员函数:
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
main函数:
// 把两个对象相加,得到 Box3
Box3 = Box1 + Box2
注意!!!!
一个类重载operator[] ()函数时,要重载operator[]两次,一个是const函数,一个是非const函数。const函数的返回值用按值传递,非const函数的返回值用引用。只实现一次重载,在某些情况下是会报错的。
34.二维指针
34.1 二维指针应用:传参