这里主要记录下我认为重要的点。
宏定义
宏定义目的是防止头文件被重复引用。
当编译器编译头文件时,判断当前宏是否被定义,如果没有定义,则定义宏,
并编译头文件,否则跳过头文件。1
#ifndef
和#pragma once
的异同
相同:
都是为了避免同一个文件被include
多次。1
2
3
4
...1
2
...不同:
#ifndef
的方式受C/C++语言标准支持。它不光可以保证同一个文件不会被包含多次,
也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。
由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,
#ifndef
会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once
的方式。#prama once
一般有编译器提供保证:同一个文件不会被包含多次。但是不能保证不同文件的
相同内容不会被重复引用。但是对于大型项目,这个方式的编译速度会更快。同时,#pragma once
兼容性不强。- 引用头文件
1-1. 使用<>
引用系统头文件系统会先在系统库文件目录下搜索头文件,它不会搜索当前工程下的目录。1
1-2. 使用""
格式引用自定义头文件编译器会现在当前工程目录下搜索头文件,然后再搜索系统库文件目录。1
- volatile关键字
该关键字表示变量可以被某些意外的因素改变。
当编译器遇到该关键字时,将不会对变量进行优化,从而提供对变量的直接访问。
如果全局变量使用volatile关键字,程序每次访问都是从变量的内存地址访问。1
2
3
4volatile int var = 1;
_asm{
mov ptr[ebp-4],10h
} 变量存储类型
C++语言中,变量有4种存储类型:extern
,static
,register
,auto
。
变量的声明:告知编译器变量的名称和数据类型。
变量的定义:编译器为变量分配存储空间。
4-1. extern存储类型
extern关键字只声明不定义变量。通常在一个应用程序中包含多个文件,如果在一个文件中
定义了一个全局变量,可能需要在其他文件中对其访问,可用extern
关键字修饰其他文件中
变量的声明,而定义源于前一个文件。
eg:
在一个文件中定义了一个全局变量var1
int var=0;
在其他文件中使用extern关键字声明全局变量var
1
extern int var=0;
4-2. static存储类型
静态变量类型,分为全局静态变量类型和局部静态变量类型。- 当使用
static
关键字标识一个局部变量时,该变量被分派在一个持久额存储区域,当函数调用结束时,
变量并不被释放,依然保持其值。当下一次函数被调用时,将应用之前的变量值。
局部静态变量只被初始化一次,但是它的作用域仍然是函数体内。
可以认为静态变量是一个在函数调用后保持其值的局部变量。 对于全局静态变量,它的作用域局限于本当前定义的文件,不能被其他文件使用extern关键字访问。
可以认为全局静态变量只是半个全局变量。(不能被其他文件共享)eg:
1
2
3
4
5
6
7
8
9
10void TestStaticVar(){
static int slocal=10;//定义一个局部静态变量
printf("%d\n",slocal);
++slocal;
}
void main(){
TestStaticVar();
TestStaticVar();
TestStaticVar();
}将输出10,11,12。
4-3. register存储类型
使用register
关键字定义变量,变量将被分派到CPU的寄存器中,因此访问速度很快。
但是,register变量只能用于局部变量或作为函数的形式参数,而不能定义全局的register变量。4-4. auto存储类型
全局变量、静态变量均为静态存储,而普通的局部变量属于动态存储。
auto 关键字表示变量将被用动态存储。默认局部变量均属于auto类型。
- 当使用
实数和0的比较
在比较实数和0时,尽量不要使用”==”,”!=”,而应该使用”<=”,”>=”运算符。1
2
3
4
5
6
7
8
9void main(){
float eps=0.0000001;//定义0的精度,相当于数学的无穷小
float fvar = 0.00001;
if(fvar>=-eps && fvar<=eps){
printf("等于0\n",fvar);
}else{
printf("不等于0\n",10);
}
}- 二维数组
在定义二维数组时,如果需要提供全部元素的初始值,可以省略第一维的长度,
但是不能省略第二维的长度。1
int arr[][4]={1,2,3,4,5,6,7,8};
- 结构体的字节对齐问题
编译器在为结构体变量分配变量空间时,保证下一个成员的偏移量为该成员数据类型长度的整数倍。使用1
2
3
4
5struct ByteAlign{
double memOne;
char memTwo;
int memThree;
};sizeof
函数测试时,发现结构体ByteAlign的变量占用16个字节。
分析:double占用8个字节,char占用1个字节,int占用4个字节。假设从0地址开始,
memOne占用0,1,2,3,4,5,6,7。
memTwo占用8。
接下来为int型分配空间,需要空出9,10,11。
memThree占用12,13,14,15。 - pair类型
pair
类型可以使两个相同或不同类型的变量关联在一起。
在使用pair类型时,需要引入utility头文件。
可以pair类型的成员,可以使用成员访问符first
和second
。1
2
3
4
5
6
7
8
using namespace std;
void main(){
pair<int,char*> list(10,"C++");//定义pair类型变量list
if(list.first==10 && list.second=="C++"){
printf("I love C++ language!\n");
}
} - const 关键字修饰指针类型
const
在类型前,用户不能修改指针常量的数据,但是可以修改指向的地址,指针变量“只读”;const
在类型后,用户可以修改变量的值,但是不能修改指向的地址,指针变量固定指向地址;const
在类型前后都有,用户既不能修改指向的地址,也不能修改指向的数据。
具体看下面的例子:1
2
3
4
5int var=10; //定义一个整型常量
const int* pvar=&var; //定义一个指针常量,对其初始化
//*pvar=20; //错误代码,不能修改指针常量的数据
int num=5; //再定义一个整型变量
pvar=# //修改指向的地址1
2
3
4
5int ivar=0; //定义一个整型常量
int* const pvar=&ivar; //定义了一个指向整型的常量指针
*par=20; //修改指针指向的数据
int inum=5; //定义一个整型变量
//pvar=&inum; //错误的代码,不能修改常量指针指向的地址1
2
3
4
5int ivar=10; //定义一个整型变量
const int* const pvar=&ivar;//定义一个常量指针常量
//*pvar=20; //非法的代码,不能够修改pvar指向的数据
int inum=10; //定义一个整型变量
//pvar=&inum; //非法的代码,不能够修改pvar指向的地址
动态存储类型分配和释放
new
和delete
搭配,malloc
和free
搭配。
使用new
运算符为数组分配空间时,不能对数组初始化,除非变量是一个对象,
并且对象的类型(类)提供了默认的构造函数。
此外,在释放使用new
运算符为数组分配的空间时,需要使用delete []
来释放。1
2int* pvar=new int[5];
delete [] pvar;对类对象类型的数组,必须这样做。
运算符的结合性和优先性
逗号表达式
C++提供了一种特殊的表达式,即逗号表达式。比如:10*5,8*9
逗号表达式的运算过程为: 先计算表达式1,再计算表达式2,依次类推。
整个表达式的值,也就是最右边的表达式的值。
注意:赋值符=
的优先级高于逗号运算符,
,一般要加括号。1
2
3int iret=2;
iret=3*5,iret*4; //iret最后的值为15
int var=(iret=3*5,iret*4);//var=60,iret=15;