概念集合
- 访问控制(数据成员,成员函数)
public:所有的函数都可以访问这些成员。
private:只有该类的成员函数或该类的友元(包括友元函数和友元类)可以访问。
protected:只对派生类开放,派生类可以通过自己的成员函数来访问。
- 构造函数和析构函数
没有参数的构造函数称为默认构造函数,构造函数是没有返回值的,在生成类对象时自动被调用。
因此如果是带参数的构造函数,则需要在构造类对象的时候,同时给出参数。
如果有多个构造函数,系统将根据给出的参数个数,来选择对应的构造函数。(跟构造函数的重载有关。)
- 复制构造函数
复制构造函数是用来将一个对象的数据成员的值赋给另一个对象的数据成员的值。
两个对象应属于同一个类。一般赋值采用对象的引用。
如果一个类中定义了一个指针变量,那么使用赋值构造函数的时候,就是将一个对象中的指针变量的地址赋给了另一个对象中的指针变量的地址。
!调用复制构造函数的三种情况
(1)使用已存在的对象来对另一个对象进行初始化。
(2)如果函数的形参是类对象,则在进行函数调用的时候,将自动调用复制构造函数。(!!注意,这就是为什么复制构造函数的参数是对象的引用,而不是对象的本身,因为如果参数是对象本身的话,就又会调用复制构造函数。这就相当于复制构造函数又调用复制构造函数,而复制构造函数的参数一直都是对象,所以就会无限地调用复制构造函数。)
但是如果函数参数是对象的引用或是指向对象的指针,就不会调用复制构造函数。
(3)如果函数的返回值是类对象,那么在执行返回语句的时候会自动调用复制构造函数。
同样,返回的如果是引用或指针不会调用复制构造函数。
- 默认复制构造函数
默认复制构造函数只能简单进行数据成员的值的复制。
- 引用
引用变量的值是不能改变的。引用变量在定义的时候就要同时进行初始化。
- 变换构造函数和变换函数
只有一个参数的构造函数称为变换构造函数。
变换函数:
用于从对象中返回一个特定类型的值。
class abc{
int name;
public:
abc(){name = 12;}
//定义变换函数
operator int()
{
return name;
}
};
int main()
{
abc a;
//调用变换函数
int name = a;
}
//返回值是12
- 静态数据成员和静态成员函数
若某个类中,某一个数据成员的值在所有对象中都是一样的,这种数据成员可以定义成静态的。静态数据成员是所有对象都共享的数据。
设置静态数据成员或静态成员函数,是因为所有对象都共享一份代码,因此能提高内存的利用效率。
静态数据成员或静态成员函数是没有this指针的,因为this指针是针对某一个具体对象的。由于静态成员函数没有this指针,这表明静态成员函数不能操作某个具体对象的数据成员。
因此静态成员函数只能操作静态数据成员。
构造函数和析构函数不能定义为静态的。
静态数据成员的定义:
class abc
{
//定义静态数据成员
static int a;
public:
static int count()
{
return a;
}
};
//静态数据成员的初始化
int abc::a = 100;
int main()
{
//访问静态数据成员,通过类或某一对象
//静态成员函数也是一样的方式
abc::a = 11
abc t;
t.a = 11
}
- 友元(友元函数,友元类)
友元函数是基于减少访问私有数据成员的开销。因为一般来说,如果要访问私有数据成员,就只能通过本类的成员函数来访问。因此定义友元函数或友元类(一般是跟类没有特别关系的),使他们可以访问私有数据成员。
class abc
{
int name;
public:
abc(){name = 11;}
//说明某函数是该类的友元函数
friend void seeName(abc a);
};
void seeName(abc a)
{
//访问该类的数据成员
cout<<a.name<<endl;
}
友元类
当类1作为类2的友元类,类1可以访问类2中所有的数据成员和成员函数。
- 运算符的重载
- 类的嵌套定义
- 结构,联合
联合:如果希望在不同时刻能够把不同类型的数据存放到同一段内存单元中,就可以使用联合来实现。关键字是union
结构:也是定义类的关键字,里面同样可以定义数据成员和成员函数,但与class不同的是对成员的默认访问控制不同。class是private,而struct是public。
- 基类,派生类
由基类继承出来的类称为派生类。
派生类可以使用基类中所有的public和protected成员,但不能直接使用private成员。(友元可以直接使用private)
继承方式决定派生类可以访问基类中的哪些成员。
//若继承方式是public,可以访问基类中protected以上成员。
//若继承方式是protected,可以访问基类中protected以上成员。
多态可以表示为同一个名字代表多个具体的对象。
- 静态结合和动态结合
静态结合指的是在编译阶段就确定好的指针与类的关系。这种结合方式在程序运行过程中是不能改变的。
cBase *p;//规定了p只能指向cBase类
cBase a;
p = &a;
p->disp();//调用a对象下的disp函数
cDerived b;//cDerived类继承cBase类
p = &b;
p->disp();//由于静态结合的方式,因此虽然将b对象的地址赋给了p,依然调用的是cBase类的disp函数
若能实现最后一行的功能,即p能调用b的disp()函数,即能实现p能指向多个对象,即p能动态地确定与类的结合关系,这种就叫动态结合。 这也叫实现了多态。
实现动态结合必须使用虚函数。
- 虚函数
在c++中,虚函数是用来向系统表明采用动态结合方式的函数,
(1)一般通过在基类的成员函数的前面加上virtual来定义。
(2)指针必须是指向基类。
即如果要实现上述功能,只需要在cBase类中的disp函数前加上virtual,将该函数定义为虚函数。
// 实现多态
#include<iostream>
using namespace std;
class cBase
{
int a;
int b;
public:
cBase()
{
a = 1;
b = 2;
}
~cBase()
{
cout<<"这是cBase的析构函数"<<endl;
}
virtual void disp()
{
cout<<"this is cBase disp,a:"<<a<<",b:"<<b<<endl;
}
};
class cDerived:public cBase
{
int c;
public:
cDerived():cBase()
{
c = 3;
}
~cDerived()
{
cout<<"这是cDerived的析构函数"<<endl;
}
void disp()
{
cout<<"-----------"<<endl;
cBase::disp();
cout<<"this is cDerived disp,c:"<<c<<endl;
}
};
int main()
{
cBase a;
cDerived b;
cBase *p;
p = &a;
p->disp();
p = &b;
p->disp();
}
实现效果:
- 多态
总的来说,多态可以归结为:
(1)多态只在基类和派生类中实现。
(2)多态通过在基类中定义虚函数实现。
(3)指针或引用只能指向基类。
(4)一旦实现上述三个条件,指向基类的指针可以指向任意一个派生类,并调用派生类中的虚函数。
- 纯虚函数
若基类中定义的虚函数没有函数体(即什么都没有定义),就将该虚函数定义为纯虚函数。
纯虚函数一般只定义在不会生成对象的基类中。
定义了纯虚函数的基类不可以生成对象,但是基类中的Public的成员函数可以被派生类调用。但纯虚函数不能被调用,因为该函数没有函数体。
- 虚表
- 继承的内存情况是什么样的?
- estern,final,virtual,static,const
变量的存储类有auto,register,static,extern
auto存储类,即自动存储类,指的是在函数内部定义的变量。自动存储类变量是在动态存储区中分配存储单元的,当函数返回的时候,这类变量中存放的数据也就消失了。
static存储类,即静态存储类,如果某一对象或变量被指定为static存储类,那么在程序执行过程中,此对象或变量会一直存在。static一般用在定义函数内部定义的变量。如果没有static的话,该变量会在函数调用完后自动消失,包括变量的地址和变量的值。加上static后,该对象或变量会一直存在。在函数外面的定义的对象或变量,不需要声明就是静态存储类。
register存储类,是寄存器存储类,为了提高某些自动类变量,函数参数的处理速度,如果在变量类型前加上register,可以通知编译器为这些变量分配寄存器处理数据。
exterm存储类,即外部存储类,如果在一个文件1中要引用另一个文件2中定义的外部变量,就在文件1中应用extern将此变量说明为外部的。即文件2中负责定义外部变量,文件1负责说明外部变量。
定义的方式:int a;
说明的方式:extern int a; //在main函数外说明。
!!注意,static修饰函数的时候,说明该函数只在该文件中有作用。正常来说,没有static修饰的函数,是可以被最终连接到一起的其他文件中的函数所调用的。
用const修饰变量,表明该变量是个常量,初始化后数值不能改变。
用const修饰对象,表明该对象的值经初始化之后不能再改变。而且经const后的对象不能通过对象访问该对象的成员函数。
- new与malloc的区别
(1)使用new不用定义头文件。
(2)使用new不用指定分配多少内存,new会自己计算,而malloc需要指定。
(3)使用malloc需要进行强制类型转换,因为malloc返回的是void *型,而new的返回值与对象类型严格匹配,不需要进行类型转换。
(4)使用malloc是在堆上分配内存,堆是操作系统自己维护的一个内存区。而new是分配了一个自由内存区。
new的简单使用方法:
a = new int(5),这个是为int型变量设定初始值。
a = new int[5],这个是申请5个int型空间。
delete a;
二维数组的内存申请:
申请char[10][80]
char (*ss)[80]; //表示ss指向一个char[80]类型的指针。
ss = new char[10][80];
delete []ss;
- 浅复制与深复制
浅复制是复制变量地址,深复制是复制变量的值。
- 如何避免重复引用头文件
https://blog.youkuaiyun.com/lixungogogo/article/details/50992298
#ifndef _H_M_
#define _H_M_
下面放声明语句
总结:条件编译语句可以防止一个文件中对头文件的重复包含。
#pragma once由编译器提供保证:
同一个文件不会被编译多次。
- 关于extern的详细讲解
http://www.cnblogs.com/rollenholt/archive/2012/03/20/2409046.html
总的来说,extern有两个作用:
(1)声明全局变量
如果全局变量在其他地方进行了定义,而在该地方以上又需要用到该全局变量,那么在可以文件首部加上声明:
extern int a;(注意,这是声明,不是定义)
全局函数默认都是有extern关键字的,因此可以被其他文件(同一个工程)引用。
(2)实现不同类型语言的文件的混用,实现混合编程
一般在一个文件都会声明一个头文件
c++引用c头文件,在c++的头文件中这么写:
#ifndef _C_FUN_
#define _C_FUN_
extern "C"
{
#include "cExample.h"
}
#endif
而引用的c语言的头文件,即cExample.h中应该这么写(将c++要引用的函数加上extern):
/* c语言头文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y); //注:写成extern "C" int add(int , int ); 也可以
#endif
/* c语言实现文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
return x + y;
}
c++实际代码文件中实现引用的过程:
// c++实现文件,调用add:cppFile.cpp
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
加了extern "C"的编译处理过程:
在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:
(1)模块A编译生成add的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。
实际c++中函数的编译过程是:
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。
_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
extern与static
extern是规定该变量或函数能被外部引用,而static则是规定该函数或变量只能在本文件中生效。
- 类的内存对齐
http://www.cnblogs.com/TenosDoIt/p/3590491.html
内存对齐是编译器为了便于CPU快速访问而采用的一项技术。
它采用有两个方面的原因:
(1)平台问题,某些硬件平台只能处理对齐后的数据。
(2)速度问题,未对齐的数据,会进行两次访存。
内存对齐:
class a
{
int a;
char c;
short b;
};
sizeof(a) 8个字节,a[0-3] c[4] b[6-7] 所以共占用字节0-7
类的对齐,以成员里面占字节数最多的成员变量的字节数作为对齐字节。分配内存空间时,应该为该字节数的整数倍。
派生类的对齐字节数,根据编译器的不同而不同。
- strcpy和memcpy的区别
strcpy用于将字符串传给一个指针变量。
extern char *strcpy(char *dest, const char *src)
存在的问题:
假设使用strcpy将a的值传给b
那么如果当b所指的内存空间不够大的时候,会出现内存溢出的情况。
为了避免出现这种问题,可以先加上长度判断。
memcpy
void *memcpy(void *dest, const void *src, size_t n);
两个函数的不同之处:
(1)memcpy需要指定复制的长度。
(2)strcpy只能复制字符串,而memcpy可以复制任何东西,包括字符数组,结构体,类。
- 宏的相关问题
宏一般在预编译的阶段起作用,用于直接替换。
https://blog.youkuaiyun.com/aheroofeast/article/details/7932762
可以用来代替宏的技术:
(1)对于常量定义
#define NUM 10
替代为:
const int NUM = 10;
存在的好处:
1. 不必担心存在多个实例的问题
2. 对于const修饰的变量,编译器一般也会对其进行优化,
(2)对于函数定义
宏可以用来替换函数
#define square(x) ((x)*(x))
替代方案:
使用inline函数
inline int square(intvalue)
{
return value*value;
}
使用inline的好处:
不需要进行函数调用。减少函数调用的开销,而且能够确保参数处理的安全性。
- 为什么main()函数要返回一个int
https://www.cnblogs.com/Tope/archive/2012/10/25/2740351.html
注意在返回的时候要有一个返回值,这样才能安全退出程序之后各寄存器会有恢复动作,如果没有返回值,虽然表面上看程序也正常结束了,但实际上它并没有退出,各寄存器并没有恢复.
https://blog.youkuaiyun.com/yangyong0717/article/details/72934102
这样程序才能传告诉操作系统是否成功执行完毕。
但是,main的返回值类型,写成void也不会错,它等效于没有return 语句的int类型。不过为了养成良好的习惯,最好还是写成int。
另外,return返回的数值由程序的作者自定。返回不同的值可以代表不同的含义,一般是代表出错的原因。传统上返回0代表程序正常结束(其它返回值代表什么含义,需要程序的开发者向程序的用户说明)。
总的来说:
返回值有两个意义:
(1)告诉操作系统程序已经安全退出,这样操作系统才能让寄存器恢复动作。
(2)返回的数值还可以代表某一些特定含义,返回0代表函数正常执行。
- printf是如何处理可变参数的
https://blog.youkuaiyun.com/shuaishuai80/article/details/6140918
函数原型: int printf(const char *format[,argument]...)
返 回 值: 成功则返回实际输出的字符数,失败返回-1.
函数说明:
在printf()函数中,format后面的参数个数不确定,且类型也不确定,这些参数都存放在栈内.调用printf()函数时,根据format里的格式("%d %f...")依次将栈里参数取出.而取出动作要用到va_arg、va_end、va_start这三个宏定义,再加上va_list.
根据参数入栈的特点从最靠近第一个可变参数的固定参数开始,依次获取每个可变参数的地址.
(1)宏va_start
通过该宏定义可以获取到可变参数表的首地址,并将该地址赋给指针ap.
(2)宏va_arg
通过该宏定义可以获取当前ap所指向的可变参数,并将指针ap指向下一个可变参数.注意,该宏的第二个参数为类型.
(3)宏va_end
通过该宏定义可以结束可变参数的获取.
- 指针和引用的区别
https://www.cnblogs.com/dolphin0520/archive/2011/04/03/2004869.html
(1)sizeof(引用) 得到的是引用指向的变量的大小,sizeof(指针)得到的是指针本身的大小
(2)有const指针,没有const引用
(3)指针的值可以为空,但引用在定义的时候就要初始化,且值不可以变。
在讲引用作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。