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
- 修饰普通变量,修改变量的存储区和生命周期,使变量存储在静态区,在main函数运行前就分配了空间,如果有初始值就用初始值初始化,否则就用默认值来初始化。
- 修饰普通函数,表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命令函数重合,可以将函数定为static。
- 修饰成员变量,使得所有的对象只保存一个该变量,而且不需要生成对象就能够访问静态成员变量。
- 修饰成员函数,使得不需要生成对象就能访问该函数,但是在static函数内不能访问非静态成员(static成员函数没有this指针)。
this指针
- this指针是一个隐含于每一个非静态成员函数的特殊指针。它指向正在被该成员函数操作的对象。
- 当对一个对象调用成员函数时,编译程序先将对象的地址赋值给this指针,然后再调用成员函数,每次成员函数存取成员变量时,会隐含使用this指针。
- 当一个非静态成员函数被调用时,自动向它传递一个隐含参数——this指针。
- this指针被隐含地声明为:Classname *const this(指针常量),这意味着不能给this指针赋值。
- 对于const成员函数:this指针的类型为 const Classname* const this。
inline内联函数
特征:
- 相当于把内联函数里面的内容写在调用内联函数的地方。
- 相当于不用执行进入函数的步骤,直接执行函数体。
- 有点像宏,却比宏多了类型检查,真正具有函数特性。
- 不能包含循环、递归、switch 等复杂操作。
- 在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
//声明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函数的处理
- inline函数代码是被放到符号表中,使用时像宏一样展开,没有调用的开销效率很高;
- 将inline函数体复制到inline函数调用处;
- 为inline函数体中的局部变量分配内存空间;
- 将inline函数的输入参数和返回值映射到调用方法的局部变量空间中;
- 如果inline有多个返回点,将其转变为inline函数块末尾的分支(使用GOTO)
优点
- 内联函数像宏函数一样在调用处进行代码展开,减少了函数调用开销:省去了参数压栈、栈帧开辟和回收,结果返回(函数结果返回时通过寄存器进行的,这样就可以少使用一次寄存器)等,提高了运行速度。
- 内联函数相比于宏函数,在代码展开时,会做安全检查或自动类型转换(同普通函数),而宏定义不会;
- 在类中声明同时定义的成员函数,自动转化为内联函数,因此内联函数可以访问类的成员变量,宏定义则不能。
- 内联函数在运行时可调试,而宏定义则不能。
缺点
- 代码膨胀,程序的总代码量增大,消耗更多的内存空间;
- inline 函数无法随着函数库升级而升级。inline函数的改变需要重新编译,不像 non-inline 可以直接链接。
- 是否内联,程序员不可控。内联函数只是对编译器的建议,是否对函数内联,决定权在于编译器。
虚函数能否为内联函数?
- 虚函数可以是内联函数,但是当虚函数表现多态性时不能内联。
- 内联是在编译期建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期间调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联(你可以声明为内联,但实际调用时是编译器来决定要不要内联)。
- 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
- sizeof 对数组,返回数组所占空间字节数;
- 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
- malloc:申请置顶字节数的内存。申请到的内存中的初始值不确定;
- calloc:为指定长度的对象分配能容纳其指定个数的内存。申请到的内存的每一位(bit)都被初始化为0;
- realloc:更改malloc之前分配的内存长度(增加或减少),当增加长度时,可能需将以前分配区的内容移到另一个足够大的区域,而新增区域内的初始值则不确定。
- 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