数据类型
整型、浮点型、字符型、布尔型、空类型(void)、指针(*
)、引用(&)
结构体(struct)、共用体(union)、枚举(enum)、类(class)
整型字节长度:
- 一般
short int
(2B) <=int
(4B) <=long int
(4B、8B)
类型转换:
- 隐式类型转换:存储空间提升原则(
short -> int-> long
,signed->unsigned
…) - 显式类型转换,用法:自右向左
(type)expr
、自左向右type(expr)
变量
可以指定变量的存储空间
1 | auto int a; |
函数
- 无返回值函数:返回语句用
return;
或不使用返回语句 - 有返回值函数:返回语句只能用
return 表达式;
main函数由操作系统调用,返回0表示正常退出,否则表示异常退出
形式参数:函数在未调用前不占用内存
参数入栈:最右边的参数先进栈
const参数:用于限制对一个对象的修改操作,函数体内不能对该对象修改。
对于基本类型,不必使用const限定参数,但对于数组、指针这种传进来的是地址的参数,就有必要了
可变参数,用法:
1 | 返回类型 func(type a,...){} //用三个点来表示可变参数,三个点只能位于最后,前面至少有一个普通的参数 |
函数原型(或函数声明),如:(注意区别于函数定义)
1 | int add(int a, int b); //参数名可以省略 |
默认参数:只能设置一次(函数声明中设置了默认参数,函数定义就不能再设置,反之也一样)
-
一般在声明时设置函数参数
-
默认参数要自右向左设置
-
默认值可以是常数或全局变量,甚至是一个函数的调用(但调用的函数的实参必须是常量或全局变量,如下)
1
void func1(int a, int b=func2(666)); //调用的函数func2传入的参数是常数666
内联函数
编译时直接将函数代码嵌入主调函数中,取消调用环节
内联函数的函数原型:
1 | inline 返回类型 funcName(形参列表); |
内联函数定义
1 | inline 返回类型 funcName(形参列表){ |
注意
- 内联函数不能是递归函数
- 内联函数内部不能用 【循环语句】和【switch】语句
- 编译器无法将内联函数嵌入时,会忽略
inline
声明,按照普通函数处理
函数重载
针对的是一组同名函数
特征:
- 函数名相同,参数列表(参数类型或个数)不同
- 返回类型不一定相同
- 函数体没有要求
调用时,编译器根据传入的实参判断该使用哪个函数
函数模板
目的:设计通用型函数,与类型无关
定义:
1 | template<typename T1, typename T2> 返回类型 funcName(参数列表){ |
如:
1 | template<typename T> T add(T a, T b){ |
模板参数列表不必都表示类型,也可以是非类型形参(调用时要传入值),如
1 | template<typename T1, int N> void func(T a){ |
内联模板函数
1 | template<typename T1, typename T2> inline 返回类型 funcName(参数列表){ |
内存结构
-
代码段(text)
存放程序执行的机器指令
-
已初始化数据段(data)
全局变量、静态变量,包括字符串、数组等常量
基本类型的常量一般被编译成指令存放在【代码区】
-
未初始化数据段(bss)
程序没有运行的时候bss段是不存在的,在运行时(main函数运行前)由操作系统根据变量类型分配内存空间
main函数运行前会把变量初始化为0,保证【全局、静态】变量在main函数运行前就已经分配好内存空间。
-
栈
存放所有局部、非静态变量;临时变量,包含函数形参、返回值
函数调用时,才分配存储空间,调用结束,释放相应内存空间
-
堆
存放动态分配的存储空间,其存储空间由程序员分配和释放,即执行到相应的代码块才会分配或释放存储空间
引用类型
主意啊,这里的"引用"要理解为名词,而不是动词
简单说来就是变量的别名,声明方式:
1 | 类型 &引用名=被引用的对象名 |
如:
1 | int x; |
引用的本质
本质是位于某个内存地址上的指定类型的对象。
上例中 r
与x
占用内存中【同一个存储单元】
其它需要注意的点:
-
引用在声明时必须指定引用的对象是谁
-
引用在声明后,不能再修改作为其它对象的引用
- 注意:函数参数是引用时,通常用const修饰,表示引用的对象不能通过引用修改,而不是说引用不能被修改(因为引用本身就不能被修改)
-
编译器不会专门开辟内存单元存储引用
-
引用的类型与被引用的类型要相同
函数返回类型是引用?
首先说明:函数返回值时会产生一个【临时变量】作为函数返回值的副本(因为函数调用后局部变量会被销毁),而返回引用时不会产生临时变量。因此
- 千万不要引用函数中的局部变量(因为如果函数销毁了,引用变量指向谁?)如
- 如果函数参数是引用,可以返回引用,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 int& func(int &a, int&b){
return a;
}
int main()
{
int x=1, y=2, z=9;
func(x, y) = z; //因为函数内return a; a引用的是x,所以这相当于x=z;
cout << "x=" << x <<endl;
cout << "y=" << y <<endl;
}
//运行结果:
//x=9
//y=2
常引用
如果是常引用,表示不能通过引用来修改对象的值
1 | int a=66; |
C++对象创建2种方式
1 |
|
上面就是具体的演示代码,下面分析一下几种创建方式:
(1)第一种和第二种:这两种没什么区别,一个隐式调用,一个显式调用,两者都是在进程虚拟地址空间中的栈中分配内存。用这种方法创建的对象,内存分配到栈里(Stack)。使用 “.” 而不是 “->” 调用对象的方法。当程序离开对象的使用范围(如方法结束,一个程度块的最后{}),范围内的栈中的对象会自动删除,内存自动回收。这是创建对象最简单的方式栈是系统数据结构,对于线程/进程是唯一的,它的分配和释放由操作系统决定,不需要由开发者来管理。在执行函数时,函数内局部变量的存储单元可以在栈上创建,函数执行完毕,系统会自动释放这些存储单元。
(2)第三种:使用了new,在堆中分配了内存,堆上的内存分配,亦称动态内存分配。程序在运行的期间用malloc申请的内存,这部分内存由程序员自己负责管理,其生存期由开发者决定:在何时分配,分配多少,并在何时用free来释放该内存。这是唯一可以由开发者参与管理的内存。使用的好坏直接决定系统的性能和稳定。
注意:栈中内存的分配和管理由操作系统决定,而堆中内存的分配和管理由管理者决定。在堆中的对象不会自动删除,内存不会自动回收,所以new一个对象使用完毕,必须调用delete,释放内存空间。也就是说,new和delete必须成对出现
C++大、小根堆
默认创建大根堆(会自动对元素排序,然后从大到小插入素),遵循FIFO原则(即大的先进,也先出)
创建小根堆要加入 greater
参数
1 | priority_queue<int> s; |
常用操作
1 | s.push(1); //插入队尾 |