C++知识点汇总

const

const多文件问题

默认情况下,const对象仅在当前文件有效,一个例子:

//const.h
#include <iostream>
void print_const_int();
//const.cpp
#include "const.h"
//const.cpp中定义一个常量ci
const int ci = 0;

int main()
{
	std::cout << "const.cpp: ci = "<<ci << std::endl;
	std::cout << "const.cpp: &ci = " << &ci << std::endl;
	print_const_int();
}
//const2.cpp
#include "const.h"
//const.cpp中也定义一个常量ci
const int ci = 1;

void print_const_int()
{
	std::cout << "const2.cpp: ci = " << ci << std::endl;
	std::cout << "const2.cpp: &ci = " << &ci << std::endl;
}

从运行结果可以看到:const.cpp和const2.cpp中都定义了const常量ci,并且两个常量ci互不影响
在这里插入图片描述

如果希望在多个文件中共享一个const常量,解决办法是:对于const变量无论是声明还是定义都添加extern 关键字,下面一个例子:

//const.h
#include <iostream>
void print_const_int();
//const.cpp
#include "const.h"

//定义这里必须加上extern
extern const int ci = 0;
int main()
{
	std::cout << "const.cpp: ci = "<<ci << std::endl;
	std::cout << "const.cpp: &ci = " << &ci << std::endl;
	print_const_int();
}
//const2.cpp
#include "const.h"

//这里加上extern表名是共享的const变量
extern const int ci;

void print_const_int()
{
	std::cout << "const2.cpp: ci = " << ci << std::endl;
	std::cout << "const2.cpp: &ci = " << &ci << std::endl;
}

从运行结果可以看出:加了extern关键字可以令多个文件共享const常量
在这里插入图片描述

const在类中
// 类
class A
{
private:
    const int a;                // 常对象成员,只能在初始化列表赋值

public:
    // 构造函数
    A() { };
    A(int x) : a(x) { };        // 初始化列表

    // const可用于对重载函数的区分
    int getValue();             // 普通成员函数
    int getValue() const;       // 常成员函数,不得修改类中的任何数据成员的值
};

const与指针

1.常量指针:
(常量符号const再到指针符号*)
在这里插入图片描述

//常量指针,不能通过该指针修改其指向的内容的值
const int a = 0;
const int b = 0;
const int* p1 = &a;//等式左右两边都是const int*类型
p1 = &b;//允许
//常量指针,不能通过该指针修改其指向的内容的值
int a = 0;
int b = 0;
const int* p1 = &a;//等式左边是const int*类型,右边是int*;但仍然被允许,这是一个特例,见C++primer第五版P56
p1 = &b;//允许
//常量指针,不能通过该指针修改其指向的内容的值
int a = 0;
int b = 0;
const int* p1 = &a;
a = 5;//允许!
*p1 = 5;//不允许

2.指针常量:
(指针符号*再到常量符号const)
指针常量的一个例子就是this指针,this指针就是一个指针常量:Classname * const this
在这里插入图片描述

//指针常量,一直指向“同一个地址”,且该地址中必须是非常量
const int a = 0;
int*const p2 = &a;//不允许,因为a是const类型,是常量
//指针常量,一直指向“同一个地址”,且该地址中必须是非常量
int a = 0;
int*const p2 = &a;//允许,因为a是非常量
*p2 = 5;//允许
a = 5;//允许
//指针常量,一直指向“同一个地址”,且该地址中必须是非常量
int a = 0;
int b = 0;
int*const p2 = &a;//允许,因为a是非常量
p2 = &b;//不允许!

3.指向常量的常量指针:
在这里插入图片描述

//指向常量的常量指针,综合和指针常量和处理指针的特点
int a = 0;
const int* const p3 = &a;//等式左边是const int*类型,右边是int*;但仍然被允许,这是一个特例,见C++primer第五版P56
a = 5;//允许
*p3 = 5;//不允许
//指向常量的常量指针,综合和指针常量和处理指针的特点
const int a = 0;
const int b = 0;
const int* p3 = &a;//等式左右两边都是const int*类型
p3 = &b;//不允许
//指向常量的常量指针,综合和指针常量和处理指针的特点
const int a = 0;
const int* p3 = &a;//等式左右两边都是const int*类型
*p3 = 5;//不允许,原因有2:1.不允许通过p3修改指向的值,2.a本来就是常量
常量在函数中
// 函数
void function1(const int Var);           // 传递过来的参数在函数内不可变
void function2(const char* Var);         // 参数指针所指内容为常量
void function3(char* const Var);         // 参数指针为常指针
void function4(const int& Var);          // 引用参数在函数内为常量

// 函数返回值
const int function5();      // 返回一个常数
const int* function6();     // 返回一个指向常量的指针变量,使用:const int *p = function6();
int* const function7();     // 返回一个指向变量的常指针,使用:int* const p = function7();

static

  1. 修饰普通变量,修改变量的存储区和生命周期,使变量存储在静态区,在main函数运行前就分配了空间,如果有初始值就用初始值初始化,否则就用默认值来初始化
  2. 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命令函数重合,可以将函数定为static。
  3. 修饰成员变量,使得所有的对象只保存一个该变量,而且不需要生成对象就能够访问静态成员变量。
  4. 修饰成员函数,使得不需要生成对象就能访问该函数,但是在static函数内不能访问非静态成员(static成员函数没有this指针)。

this指针

  1. this指针是一个隐含于每一个非静态成员函数的特殊指针。它指向正在被该成员函数操作的对象
  2. 当对一个对象调用成员函数时,编译程序先将对象的地址赋值给this指针,然后再调用成员函数,每次成员函数存取成员变量时,会隐含使用this指针
  3. 当一个非静态成员函数被调用时,自动向它传递一个隐含参数——this指针。
  4. this指针被隐含地声明为:Classname *const this(指针常量),这意味着不能给this指针赋值。
  5. 对于const成员函数:this指针的类型为 const Classname* const this。

inline内联函数

特征:

  1. 相当于把内联函数里面的内容写在调用内联函数的地方。
  2. 相当于不用执行进入函数的步骤,直接执行函数体。
  3. 有点像宏,却比宏多了类型检查,真正具有函数特性。
  4. 不能包含循环、递归、switch 等复杂操作。
  5. 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
//声明1(加inline)
inline int fun(int first,int next,...);

//声明2
int fun(int first, int second,...);

//定义
inline int fun(int first,int second,...)
{
	... ...
}

//类内定义,隐式内联
class A
{
	int fun()		//隐式内联
	{
		return 0;
	}
}

//类外定义,默认非内联
class A
{
	int fun();
}
inline int A::fun()		//需要显示定义
{
	return 0;
}

编译器对inline函数的处理

  1. inline函数代码是被放到符号表中,使用时像宏一样展开,没有调用的开销效率很高;
  2. 将inline函数体复制到inline函数调用处;
  3. 为inline函数体中的局部变量分配内存空间;
  4. 将inline函数的输入参数和返回值映射到调用方法的局部变量空间中;
  5. 如果inline有多个返回点,将其转变为inline函数块末尾的分支(使用GOTO)

优点

  1. 内联函数像宏函数一样在调用处进行代码展开,减少了函数调用开销:省去了参数压栈、栈帧开辟和回收,结果返回(函数结果返回时通过寄存器进行的,这样就可以少使用一次寄存器)等,提高了运行速度。
  2. 内联函数相比于宏函数,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义不会;
  3. 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能
  4. 内联函数在运行时可调试,而宏定义则不能。

缺点

  1. 代码膨胀,程序的总代码量增大,消耗更多的内存空间;
  2. inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。
  3. 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。

虚函数能否为内联函数?

  1. 虚函数可以是内联函数,但是当虚函数表现多态性时不能内联。
  2. 内联是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期间调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联(你可以声明为内联,但实际调用时是编译器来决定要不要内联)。
  3. inline virtual可以内联的时候是:编译器知道所调用的对象是哪个类,这只有编译器具有实际对象而不是对象的指针或引用时才会触发

虚函数与内联举例

#include <iostream>
using namespace std;
class Base
{
pubilc:
	inline virtual void who()//其实已经是隐式声明为inline了
	{
		cout << "I am Base" << endl;
	}
	virtual ~Base(){}
};
class Derived : public Base
{
public:
	inline void who()
	{
		cout << "I am Dervied" <<endl;
	}
};

int main()
{
	//这里的虚函数who()是通过实际的对象(而不是指针或者引用)来调用的,
	//因此在编译期就能确定调用的是Base类中的who()函数,也就是说可以内联
	Base b;
	b.who();

	//此处的虚函数是由指针来调用的,呈现多态性;
	//在编译器无法确定是调用哪个who(),因此不能内联
	Base *p = new Dervied();
	ptr->who();
}

sizeof

  1. sizeof 对数组,返回数组所占空间字节数
  2. sizeof 对指针,返回指针本身所占空间字节数。
#include <iostream>

int main()
{
	char arr[100];
	char *ptr = new char[100];
	std::cout << sizeof(arr) << std::endl;
	std::cout << sizeof(ptr) << std::endl;
	delete [] ptr;
}

在这里插入图片描述

pragma pack(n)

设定对齐字节数

#include <iostream>
//默认为8个字节
struct test1
{
	char c1;
	double d1;
	int i1;
};

//使用pragma pack(4)设置为4个字节
#pragma pack(4)
struct test2
{
	char c1;
	double d1;
	int i1;
};

int main()
{
	std::cout << "sizeof(test1) = " << sizeof(test1) << std::endl;
	std::cout << "sizeof(test1) = " << sizeof(test2) << std::endl;
}

输出结果为:

此外:使用pragma pack(push)和pragma pack(pop)可以恢复默认的对齐字节数;

//使用pragma pack(push)和pragma pack(pop)可以恢复默认的对齐字节数
#include <iostream>

//使用pragma pack(4)设置为4个字节
#pragma pack(push)  //提前保存默认的对其字节数
#pragma pack(4)		//将默认对其字节数设置为4
struct test2
{
	char c1;
	double d1;
	int i1;
};
#pragma pack(pop)	//恢复默认的对其字节数

//默认为8个字节
struct test3
{
	char c1;
	double d1;
	int i1;
};

int main()
{
	std::cout << "sizeof(test1) = " << sizeof(test2) << std::endl;
	std::cout << "sizeof(test1) = " << sizeof(test3) << std::endl;
}

输出结果为:
在这里插入图片描述

左值与右值

左值指的是既能够出现在等号左边又能出现在等号右边的变量(或表达式)。
右值是指只能出现在等号右边的变量(或表达式)。

int a;
int b;
//合法:
a = 3;
b = 4;
a = b;
b = a;

//不合法:
3 = a;
a + b = 4;

在C语言中,通常来说有名字的变量就是左值(如上例中的a、b),而由运算操作(加减乘除,调用函数的返回值)所产生的没有名字的结果就是右值(如上例中的3 + 4, a + b)
暂且可以说:

  • 左值就是在程序中可以寻址的东西;
  • 右值就是无法取到它的地址的东西(不完全准确)。

引用

左值引用

常规引用,一般表示对象的身份。

右值引用

右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值。
右值引用可实现转移语义和精确传递,它的主要作用是:

  • 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
  • 能够更加简洁明确地定义泛型函数。

面向对象三大特性

封装、继承、多态。


在这里插入图片描述

多态

  • 多态,即多种状态(形态)。简单来说,我们可以将多态多要为消息以多种形式显示的能力。
  • 多态是以封装和继承为基础的。
  • C++多态的分类和实现:
    1)重载多态(编译期):函数重载,运算符重载;
    2)子类型多态(运行期):虚函数;
    3)参数多态性(编译期):类模板、函数模板;
    4)强制多态(编译期/运行期):基本类型转换、自定义类型转换

静态多态
函数重载

void do(int a);
void do(int a, int b);

动态多态——虚函数

  • 虚函数:用virtual修饰的成员函数
  • 普通函数(非类成员函数)不能是虚函数
  • static静态成员函数(没有this指针)不能是虚函数
  • 构造函数不能是虚函数

虚析构函数
如果析构函数为虚函数,则delete释放内存时,先调用子类析构函数,再调用基类析构函数,防止内存泄漏。

class Base
{
public:
	Base();			//构造函数不能为虚
	virtual ~Base()	//虚析构函数
	{
		cout << "~Base()" << endl;
	}	
};
class Son: public Base
{
public:
	~Son()
	{
		cout << "~Son()"<<endl;
	}
};
int main()
{
	Base* bptr = new Son();
	delete bptr;//因为Base是虚析构函数,所以delete释放内存时,先调用子类析构函数,再调用基类析构函数
}
纯虚函数

纯虚函数是一种特殊的虚函数,在基类中不能对纯虚函数给出有意义的实现:

virtual int func() = 0;

虚函数、纯虚函数的区别于练习

  • 如果类里声明了虚函数,虚函数既可以是实现的,也可以是空实现(即纯虚函数),那么这个类以及继承(直接或间接继承)了它的类,都会有一个虚函数指针,用于实现动态多态;
  • 普通的虚函数(非纯虚)在子类里可以不进行重载;但纯虚函数必须在子类中实现;
  • 带储蓄函数的类叫做抽象类,这种类不能直接生成对象,只能被继承并在子类里面重写其纯虚函数之后才能生成对象。

内存分配与管理

malloc、calloc、realloc、alloca

  1. malloc:申请置顶字节数的内存。申请到的内存中的初始值不确定;
  2. calloc:为指定长度的对象分配能容纳其指定个数的内存。申请到的内存的每一位(bit)都被初始化为0;
  3. realloc:更改malloc之前分配的内存长度(增加或减少),当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,而新增区域内的初始值则不确定。
  4. alloca:在栈上申请内存,程序在出栈时自动释放内存。
//malloc
int *pData = (int*) malloc (2 * sizeof(int));
//calloc
int *pData = (int*) calloc (2,sizeof(int));//分配2个int的空间
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
int main()
{
   char *str;
 
   /* 最初的内存分配 */
   str = (char *) malloc(15);
   strcpy(str, "runoob");
   printf("String = %s,  Address = %p\n", str, str);
 
   /* 重新分配内存 */
   str = (char *) realloc(str, 25);	//25是新的大小
   strcat(str, ".com");				//之前的内容将会被保留
   printf("String = %s,  Address = %p\n", str, str);
 
   free(str);
   
   return(0);
}

//运行结果,注意:Address可能会变化,也有可能不变
String = runoob,  Address = 00D81650
String = runoob.com,  Address = 00D82408
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值