C++基础知识整理

C语言中宏定义的理解

#define是C语言中提供的宏定义方法,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率

有两种常用的宏定义:
(1)简单的宏定义:
#define <宏名>  <字符串>
例: #define PI 3.1415926
(2)带参数的宏定义
#define <宏名> (<参数表>) <宏体>
例: #define A(x) x

在一个集成的开发环境如Turbo C中将编写好的源程序进行编译时,实际经过了预处理、编译、汇编和连接几个过程。其中预处理器产生编译器的输出,它实现以下的功能:
(1)文件包含
可以把源程序中的#include 扩展为文件正文,即把包含的.h文件找到并展开到#include 所在处。
(2)条件编译
预处理器根据#if和#ifdef等编译命令及其后的条件,将源程序中的某部分包含进来或排除在外,通常把排除在外的语句转换成空行。
(3)宏展开
预处理器将源程序文件中出现的对宏的引用展开成相应的宏 定义,即本文所说的#define的功能,由预处理器来完成。
(4)防止一个头文件被重复包含

优点:1、方便程序修改; 2提高程序的运行效率、

C++运行时分为哪几个区

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。

3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放。

4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放。

5、程序代码区—存放函数体的二进制代码。

C++中的 new 和C语言中的 malloc 的区别

malloc需要自己计算字节数,new会根据类型自动计算字节数
malloc返回一个空指针,需要自己进行类型转换,new 自动匹配指针类型
malloc不会自动调用类的构造函数,new会自动调用类的构造函数;
free一个对象时,不会自动调用类的构造函数,delete一个对象时会自动调用类的析构函数;
malloc,free是函数,new,delete是运算符;
new和delete:用于元素的申请
new[]和delete[]用于数组的申请;
malloc函数的原型:void *malloc(unsigned int size)

全局变量和静态变量

static全局变量与普通的全局变量有什么区别 ?

存储方式一样(都是静态存储),但是两者的作用域不同,非静态全局变量的作用域是整个源程序,在各个文件中都有效,静态全局变量只局限于一个源文件中。

static局部变量和普通局部变量有什么区别 ?

把普通局部变量改为静态后改变了生存期,生存期变长。

static函数与普通函数的区别

只在当前源文件中使用的函数应该说明为内部函数(static修饰的函数),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件.

stctic成员变量和static成员函数

-静态成员变量属于整个类所有
-静态成员变量的生命期不依赖于任何对象,为程序的生命周期
-可以通过类名直接访问公有静态成员变量
-所有对象共享类的静态成员变量
-可以通过对象名访问公有静态成员变量
-静态成员变量需要在类外单独分配空间
-静态成员变量在程序内部位于全局数据区 (Type className::VarName = value)

-静态成员函数是类的一个特殊的成员函数
-静态成员函数属于整个类所有,没有this指针
-静态成员函数只能直接访问静态成员变量和静态成员函数
-可以通过类名直接访问类的公有静态成员函数
-可以通过对象名访问类的公有静态成员函数
-定义静态成员函数,直接使用static关键字修饰即可

C++多态的概念

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。。多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,就是用基类的引用指向子类的对象,也可以说是同一种事物表现出的多种形态。
实现原理:
多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为。与之相对应的编译时绑定函数称为静态绑定。多态是面向对象编程的核心思想之一,能够很有效地提高程序的可扩充性。因此我们有必要深入探索一下它的实现原理。理解了原理才能更好的使用。
示例代码:

#include <stdio.h>
#include <string>
 
#define trace(fmt, ...) printf("[trace] %s:%s:%d " fmt, __FILE__, __FUNCTION__, __LINE__, ##__VA_ARGS__)
 
class IClient
{
	public:
		IClient(){};
		virtual ~IClient(){};
		virtual ssize_t recv(char *buff, size_t len) = 0;
};
class CStreamClient: public IClient
{
	public:
		CStreamClient(){};
		~CStreamClient(){};
		ssize_t recv(char *buff, size_t len)
		{
			trace("recv %d bytes in %p\n", len, buff);
			return len;
		}
};
int main(int argc, char **argv)
{
	CStreamClient streamclient;
	IClient &client = streamclient;
	client.recv(NULL, 0);
	return 0;
}

拥有虚函数的类都有属于自己的虚表存放在.text段中,实例化后对象拥有一个内建变量_vptr虚表指针,它指向了实际对象的虚表,通过虚表能够得到实际对象的虚函数地址。

关于mecpy函数的问题

memcpy函数的原型为:void *memcpy(void *destin, void *source, unsigned n);
如果 destin 和 source 的区域存在重叠呢?代码如下:

#include <string.h>
#include <stdio.h>
#include <iostream>

int main()
{
	char a[11] = "1234567890";
	memcpy(&a[2], a, 5);
	printf("%s", a);
	int d;
	std::cin >> d;
	return 0;
}

结果:1212345890

C++继承和派生,内部访问,外部访问

继承:保持已有类的特性来构造新类的过程称为继承
派生:在已有类的基础上新增自己的特性而产生新类的过程称为派生
内部访问:类中定义的方法访问本类中的成员变量称为内部访问,内部访问可以访问公有、私有和保护变量
外部访问:通过类的对象来直接访问对象的成员变量,只能访问公有成员变量,如 tree.leaf
继承方式分为三类,public类型, protected类型,private类型
公有继承(public)时,对基类中的公有成员和保护成员的访问属性保持不变,而对基类的私有成员则不能访问。
保护继承(protected)时,基类的公有成员和保护成员被派生类继承后变成派生类的保护成员,而基类的私有成员在派生类中不能访问。
私有继承(private)时,基类的公有成员和保护成员被派生类继承后变成派生类的私有成员,而基类的私有成员在派生类中不能访问。

C++类的三种关系,不相关、继承、复合

不相关是指两个不同的类之间没有关系,继承是指指两个类之间满足继承关系,其中 B 继承自 A 表示 B是一个A,比如女人、男人都是人, 那么女人男人就可以从人那里继承而来。复合关系是指一个类包含一个类,比如一个二维点类(point)含有 x、y 坐标,一个圆类(circle)含有一个圆心和半径,因此Circle 类应该包含 Point 类。他们之间应该是复合关系。

C++三种类型成员

public, protected, private, 默认是 private 类型

C++ 派生类成员覆盖基类成员

如果C++中派生类的成员与基类相同,会在内存中存在一份与基类同名的成员,比如:

class base {
	int j;
public:
	int i;
	void func();
};

class derived : public base{
public:
	int i;
	void access();
	void func();
};

void derived::access() {
 j = 5;	//error
 i = 5;	//引用的是派生类的i
 base::i = 5; //引用的是基类的i
 func();	//派生类的
 base::func();	//基类的
} 
derived obj;
obj.i = 1;
obj.base::i = 1;

一般不会定义同名的成员变量,会定义同名的成员函数

派生类的构造函数和析构造函数

  • 构造派生类时,总是先执行基类的构造函数,再执行派生类的构造函数。基类构造函数可以显式调用或者隐式调用。
  • 在析构派生类时,先执行派生类的析构函数,再自动调用基类的析构函数。基类析构函数是自动调用的。

虚函数

在类的定义中,前面有 virtual 关键字的成员函数就是虚函数,virtual 关键字只用在类定义里的函数声明中,写函数体时不用 virtual。构造函数和静态成员函数不能是虚函数。

C++引用和指针

C++引用的底层也是指针实现的,那C++为什么还需要引入引用呢?
1.引用是为了支持C++运算符重载,比如,使用引用传递参数实现运算符重载,重载后使用运算符时就成了 a = b - c ,这样与运算符的使用比较相近,但是写 a = &b - &c 就显得很奇怪。
2.使用指针经常容易犯错误:1.操作空指针,2.操作野指针,3.不知不觉地改变了指针的值
3.使用引用的特性与好处:1.不存在空引用,2.必须初始化,3.一个引用永远指向他初始化的那个对象

虚析构函数

虚析构函数存在的原因:通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数。但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后再调用基类的析构函数。
解决方法:把基类的析构函数声明为 virtual,派生类的析构函数可以不用 virtual 进行声明;通过基类的指针删除派生类对象时,首先调用派生类的析构函数,再调用基类的析构函数。

纯虚函数和抽象类

  • 纯虚函数:没有函数体的虚函数,定义方式:再函数后面加 “= 0”。
  • 包含纯虚函数的类叫抽象类
  • 抽象类只能作为基类来派生新类使用,不能创建抽象类的对象
  • 抽象类的指针和引用可以指向抽象类派生出来的类的对象
  • 在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部不能调用纯虚函数
  • 如果一个类从抽象类派生而来,那么当且仅当它实现了基类中所有的纯虚函数,它才能成为非抽象类

标准输入输出

  • cin 对应于标准输入流,用于从键盘读取数据,可以被重定向为从文件中读取数据
  • cout 对应标准输出流,用于向屏幕输出数据,也可以重定向为向文件写入数据
  • cerr 对应于标准错误输出流,用于向屏幕输出出错信息
  • clog 对应于标准错误输出刘,也用于向屏幕输出出错信息
  • cerr 和 clog 的区别在于 cerr 不适用缓存区,clog使用缓存区
    例子:freopen("test.txt", "w", stdout);将标准输出重定向到 test.txt文件

函数模板和类模板

  • C++提高程序可重用性有两种机制,一种机制叫做继承,另一种机制叫做泛型,使用模板的程序设计就叫做泛型程序设计。
  • 函数模板:
template <class 类型参数1, class 类型参数2, ......>
返回值类型 模板名(形参表)
{
	函数体
};
template <class T>
void Swap(T & x, T & y)
{
	T temp = x;
	x = y;
	y = tmp;
}
  • 编译器由模板生成函数的过程称为模板的实例化,实例化函数模板有两种方式:通过参数实例化函数模板不通过参数实例化模板
  • 通过参数类型实例化:
int a = 1;
int b = 2;
double c =3.5;
double d = 4.6;
Swap(a, b);
Swap(c, d);
  • 不通过参数实例化,自行指定类型:
template <class T>
T Inc(T n)
{
	return 1 + n;
}
int main()
{
	cout << Inc<double>(4)/2; //输出 2.5
	return 0}
  • 函数模板可以重载,只要他们的形参表或类型参数表不同即可。
  • 在有多个函数和函数模板名字相同的情况下,编译器如何处理一条函数调用语句
    1)先找参数完全匹配的普通函数(非由模板实例化而得的函数)。
    2)再找参数完全匹配的模板函数
    3)再找实参经过自动类型转换后能匹配的普通函数
    4)上面的都找不到,则报错。
    注意:编译器在匹配模板函数时,不进行类型自动转换
  • 类模板
    为了多快好省地定义出一批相似的类,可以定义类模板,然后由类模板生成不同的类
    类模板的写法:
template<class 类型参数1, class 类型参数2, ......> //类型参数表
class 类模板名
{
	成员函数和成员变量
};
可以将类型参数列表中的 class 改为 typename
template<typename 类型参数1, typename 类型参数2, ......> //类型参数表
class 类模板名
{
	成员函数和成员变量
};

类模板的用法

类模板名<真实类型参数表> 对象名(构造函数实参表);
Pair<string, int> student("Tom", 19);
  • 编译器由类模板生成类的过程叫类模板的实例化,由类模板实例化得到的类,叫模板类。同一个类模板的两个模板类是不兼容的。
  • 类模板的“<类型参数表>"中可以出现非类型参数
template <class T, int size>
class CArray{
	T array[size];
	public:
	void Print()
	{
		for(int i = 0; i < size(); i++)
			cout << array[i] << endl;
	}
};

在C++中调用C语言代码

C++中使用了 mangle 技术,对函数重载的函数名加上编译器中自定义规则的表示符,编译后同名函数的名称会不一样,C语言不支持重载,如果在C++中想要调用C语言开发的一些API,就需要使用 extern “C” 修饰函数声明。为了让C语言和C++都能够调用同一个API,可以在 extern “C” 的基础上使用条件编译。代码如下所示:

//sum.h文件声明
#ifndef __SUM_H
#define __SUM_H
 
#ifdef __cplusplus
extern "C" {
#endif
int sum(int a, int b);
#ifdef __cplusplus
}
#endif
#endif
 
//sum.c文件实现
#include "sum.h"
int sum(int a, int b) {
return a + b;
}

C++中重载和重写的区别

  • 重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数。
  • 重写是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,知识函数的实现体不一样。

C++如何防止内存泄漏

  • 在类的构造函数和析构函数中要匹配地使用 new/delete
  • 在代码中使用 new 单个对象或者基本类型之后,要记得用 delete 清除,使用 new 创建了一个数组,比如 new int a[10],要使用 delete[] 删除数组。
  • 要将基类的析构函数定义成虚函数
  • 不要手动管理内存,可以尝试在适用的情况下适用智能指针(智能指针是存储指向动态分配(堆)对象指针的类,能够在适当的时间自动删除指向的对象)。
  • 在编程时尽量少用 new 和 delete,需要的动态内存时可以隐藏在 RAII 对象中。(Resource Acquisition Is Initialization,利用的就是C++构造的对象最终会被销毁的原则。RAII的做法是使用一个对象,在其构造时获取对应的资源,在对象生命期内控制对资源的访问,使之始终保持有效,最后在对象析构的时候,释放构造时获取的资源)

与Const有关的问题

常量指针和指针常量

常量指针是指向常量的指针,不能通过常量指针更改所指向变量的值;指针常量是指针值不能更改的指针,指针的值不可更改。两种指针的定义方式也不一样。

//指向常量的指针
const int a = 3;
const int * pa = &a;
int * ptra = &a;	//error!

//指针常量
int num = 0;
int * const ptr = &num;
ptr = ptr;		//error! ptr 的值已经不能被修改

const 与函数的用法

  • 类中的 const 成员函数(常量成员函数)
    类中的常量成员函数不能修改类中的成员数据,也不能调用类中的非常量成员函数,否则编译器会报错。定义常量成员函数的方法:
class statck
{
	int num;
	int getNUm() const
	{
		return num;
	}
}
  • const

C++构造函数的问题

  • 在C++中注意构造函数不能调用构造函数,因为在构造函数中生成构造函数只会调用一个零时对象,零时对象会立马析构。比如如下代码:
#include<iostream>
#include<string>
using namespace std;
class Copy_construction {
public:
    Copy_construction(int a,int b,int c)
    {
        this->a = a;
        this->b = b;
        this->c = c;
        cout << "这是Copy_constructiond的有3个默认参数的构造函数!  "<<this->a<<" "<<this->b<<" "<<this->c<<endl;
    }

    Copy_construction(int a, int b)
    {
        this-> a= a;
        this->b = b;
        Copy_construction(3, 4, 5);
        cout << "这是Copy_constructiond的有2个默认参数的构造函数!  " << this->a << " " << this->b << endl;
    }
    ~Copy_construction()
    {
        cout << "Copy_construction对象被析构了! "<<this->a << " " << this->b << " " << this->c << endl;
    }

    int getC()
    {
        return c;
    }
private:
    int a;
    int b;
    int c;

};


int run()
{
    Copy_construction aa(1, 2);
    cout << aa.getC() << endl; //c的值为垃圾值,因为匿名对象被创建有立即析构了                                  //就算用不析构的方式,也是垃圾值,因为c是不同对象中的元素                    //在2个参数的构造函数中,没有显式初始化c,不能通过构造其他对象而在本构造对象中访问未初始化的数据
    return 0;
}
int main()
{

    run();
    cout << "hello world!\n";
    return 0;
}

结果:
在这里插入图片描述可以看出,在参数较多的构造函数中,不能简单的采用参数较少的构造函数来简化流程。
但是目前有两种办法:
1)采用 placement new 技术,new(this) Copy_construction(a, b); this->c = c;
2)类似于派生类构造函数调用基类构造函数:

Copy_construction(a, b, c) : Copy_construction(a, b)
{
	this->c = c;
}

不过这依赖于编译器的实现。
最稳妥的办法还是采用每个构造函数都写一遍,多点代码量罢了。

  • 8
    点赞
  • 85
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值