C++编程常用技术
第一个C++程序
cpp
#include<iostream>
第一句不是C++语句,是一个预处理语句,编译器的预处理器把输入输出流的标准头文件包括在本程序中,所以不需要在句末加分号;
include一个文件,就是把这个文件的所有内容都加进来。
#include<>常用来包含系统提供的头文件,编译器会到保存系统标准头文件的位置查找头文件;
#include""常用于包括程序员自己编号的头文件,用这种格式时,编译器先查找当前目录是否有指定名称的头文件,然后从标准头目录中进行查找
当使用<iostream.h>时,相当于在C中调用库函数,使用的是全局命名空间。
不是<string.h>的升级版
命名空间是为了让大家类名共存而不至于引起冲突而设计的。C++标准函数库的所有元素都被声明在一个命名空间中,即std。
注意,最好不要在头文件中使用命名空间,否则容易造成命名冲突。
函数
函数模板
建立一个通用函数,函数类型和形参不具体指定,用一个虚拟类型代替。
调用时,系统根据实参类型来取代虚拟类型。
定义函数模板的一般格式是
cpp
template<typename T>
例子
cpp
#include<iostream>
using namspace std;
template<typename T>
T min(T a,T b,T c){
if(a>b)a=b;
if(a>c)a=c;
return a;
}
int main(){
int a=1,b=2,c=3;
cout<<min(a,b,c)<<endl;
long long a1=1000,b1=2000,c1=3000;
cout<<min(a1,b1,c1)<<endl;
return 0;
}
比起函数重载,只适用于函数个数相同而类型不同的情况。
数组
字符数组
C++用'\0'来标识一个字符串的结束。。
strlen与sizeof的区别如下
- 前者参数必须是字符型指针char *,且必须是以'\0'结尾的,数组名作为参数传入时,已经退化为指针了。功能返回字符串长度。
- sizeof在编译时就计算好了,计算数据空间的字节数。sizeof常用于返回类型和静态分配的对象,结构或数组所占的空间,返回值跟对象,结构,数组所存储的内容没有关系。
例子
cpp
char a[10]="helo";
char *str="hello";
sizeof(a);//为10
sizeof(str);//指针所占大小,即4。
任意类型的指针都占4个字节。
还可以sizeof(函数名),即函数返回类型的空间大小,返回类型不能是void。
指针
概念
程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。比如int a=4变量这种
数组与指针
C++中,数组名代表数组第一个元素的地址,如下程序定义了两个变量:
cpp
int *p;
int a[10];
若p=a等价于p=&a[0]
数组指针,也称行指针(是一个指针变量,专门指向二维数组的)
假设定义int (*p)[n],p+1时,p要跨过n个整型数据的长度。
例子
cpp
int a[3][4];
int (*p)[4];
p=a;//将二维数组的首地址赋给p,也就是a[0]或&a[0][0];
p++; //执行后,p跨过行a[0][],指向了行a[1][]
指针数组。(多个指针变量)
int *p[n];这是一个整型指针数组,它有n个指针类型的数组元素。
这里P+1,P=a这些操作都是错的,因为p是个不可知的表示。
只有p[0],p[1]这些指针变量用来存放变量地址。
可以这样*p=a赋值这里 *p表示指针数组第一个元素的值,a的首地址的值。
二维数组赋给一指针数组
cpp
int *p[3];
int a[3][4];
for(int i=0;i<3;++i)
{
p[i]=a[i];
}
表示数组中第i行j列一个元素
*(p[i]+j)
*( *(p+i)+j)
(*(p+i))[j]
其中优先级()>[]>*
字符串与指针
cpp
char *str1="abc";//abc是常量被放到程序的常量区,不能被修改。
string str="abc";
char str2[]="abc";
字符串指针变量存放首地址可以改变指向不同的字符串,但是不能改变所指向的字符串常量。
后两者都有单独的连续存储空间。
函数与指针
每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址,有了指向函数的指针变量后,就可以用该指针变量调用函数了。
函数指针声明方法举例
cpp
int func(int a);
int (*f)(int a);//*的括号不能省略
f=&func;
(*f)(10)//这样就调用了
引用
用于为一个变量起一个别名。
函数执行期间,不可以把一个应用再作为其他变量的引用。
引用作为参数
将一般变量作为函数的参数,传给形参的是变量的值,传递是单向的。
如果在执行函数期间形参的值发生变化,并不传回给实参。
因为在调用函数时,形参和实参不是同一个存储单元。
使用引用传递函数的参数时,在内存中并没有产生实参的副本,而是对实参直接操作。
当使用一般变量传递函数的参数时,当函数发生调用时,需要给形参分配存储单元,形参变量是实参变量的副本。
如果传递的是对象,还将调用拷贝构造函数。
所以当函数参数较大时,用引用效果更好。
使用指针达到同样效果,但是还是要给形参分配存储单元
常引用
要提高程序效率,又要数据不能被改变,就用常引用。
普通引用不能指向常量。
能用const引用就尽量用const引用。
结构体,公用体,枚举
共用体
用关键字union定义,可以定义多种不同的数据类型,里面的数据共享一段内存,不同时间里保存不同的数据类型的长度的变量,以节省空间。
但同一时间只能存储其中一个成员变量的值。
0x意思是十六进制数
可以使用union判断系统是大端还是小端存储。代码如下
cpp
#include<iostream>
using namespace std;
union TEST{
short a;
char b[sizeof(short)];
};
int main(){
TEST test;
test.a=0x0102;
if(test.b[0]==0x01&&test.b[1]==0x02)cout<<"大端"<<endl;
else if(test.b[0]==0x02&&test.b[1]==0x01)cout<<"small endian."<<endl;
return 0;
}
小端符合人的感受。高位存高地址。
16进制转2进制即4位,0x01,即8位二进制,也就是一个字节。
几乎所有网络协议都是采用big endian的方式来传输数据的,当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换称为网路字节序后再进行传播。
枚举
实际问题中,有些变量的取值被限定在一个有限的范围内。
枚举类型是一种基本类型,不是构造类型,因为它不能再分解为任何其他基本类型。
声明方式
cpp
enum weekday{sun,mou,tue,wed,thu,fri,sat}
//定义变量
enum weekday a,b,c;
//或者
enum weekday{sun,mou,tue,wed,thu,fri,sat}a,b,c;
//或者
enum {sun,mou,tue,wed,thu,fri,sat}a,b,c;
枚举值是常量不是变量,不能在程序中用赋值语句再对它赋值。
错误用法
cpp
sun=5;
mon=2;
sun=mon;
只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。
cpp
a=sum;
b=mon;//正确
//错误;
a=0;
b=1;
默认第一个枚举值为0
默认枚举值为前面加1
结构体,共用体在内存单元占用字节数的计算
float4字节
union的字节数计算
cpp
union A{
int a[5];
char b;
double c;
};
int main(){
cout<<sizeof(A)<<endl;
return 0;
}
打印24个字节。
union变量共用内存,应以最长的为准,但是结果却不是4*5=20个字节,因为还需要以union内最长的变量进行默认对齐。
double8字节,要对齐就只能24了。
把int a[5]编程inta[7],那么对齐结果又变成32.
struct字节数计算
cpp
struct B
{
char a;
double b;
int c;
};
int main()
{
cout<<sizeof(B)<<endl;
return 0;
}
打印出来24
char偏移量0,占用1字节,然后补齐7字节,达到double对齐,double占用8字节,现在偏移量是16字节,int可以直接存储,占用4字节。现在总共就是20字节。
但是它还不是结构体内最长的空间类型的字节数的倍数,这里就是需要达到double8字节的倍数,即24。
混合结构体大小的计算
cpp
typedef union{
long i;
int k[5];
char c;
}UPDATE;
struct data{
int cat;
UPDATE cow;
double dog;
}too;
UDATE temp;
int main()
{
cout<<sizeof(struct data)+sizeof(temp)<<endl;
}
64为机器运行执行结果是40+24即64。
同理temp是24字节,然后struct就是4+24+8=36,还需要与8对齐,那么就是40字节。
预处理
主要4中预处理:宏定义,文件包含,条件编译和布局控制。
常用宏定义命令
#define用来将一个标识符(宏名)定义为一个字符串(替换文本)。
在简单宏定义的使用中,当替换文本所表示的字符串是一个表达式时,需要加上括号,否则容易引起误解和误用。
比如
cpp
#define N 2+9
int main()
{
int a=N*N;
cout<<a<<endl;
return 0;
}
因为宏展开是在预处理阶段完成的,这个阶段把替换文本只是看作一个字符串,并不会有任何的计算发生,在展开时是在宏N出现的地方只是简单地使用串2+9来代替N,并不会增添任何的符号。所以该程序展开后的结果是a=2+9*2+9,计算后的结果为29。实际上本意可能是11 *11=121
带参数的宏定义的声明格式如下所示:
#define 宏 (参数表列) 宏
例:#define A(x) x
cpp
#define area(x) x*x
这样的话可能出现问题
比如调用者
cpp
int y=area(2+2);
那么结果就是2+2*2+2。为8,本意可能是4 *4=16
初步解决
cpp
#define area(x) (x)*(x)
但是调用还可能是
cpp
int y=area(2+2)/area(2+2);
那么结果就是(2+2)*(2+2)/(2+2) *(2+2),结果是16
完整版解决应该是
cpp
#define area(x) ((x)*(x))
do while(0)的妙用
假设一个宏
cpp
#define Foo(x) {\
statement one;\
statement two;\
}
然后假设代码这样写
cpp
if(condition)
Foo(x);
else
...;
那么展开就是
cpp
if(condition)
statement one;
statement two;
else
...;
这样else就没能和if连在一起,编译错误了
而这样使用
cpp
#define Foo(x) do{\
statement one;\
statement two;\
}while(0)
展开就是
cpp
if(condition)
do{
statement one;
statement two;
}while(0)
else
...;
这样就没问题,因为do while(0)相当于单个语句。
条件编译
常见形式
cpp
#ifdef 标识符
程序段1
#else
程序段2
#endif
作用:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。else部分也可以没有。
另外形式
cpp
#if 表达式
程序段1
#else
程序段2
#endif
作用:当指定的表达式值为真(非零)时就编译程序段1,否则编译程序段2。
这里程序段既可以是语句组,也可以是命令行。
extern C块的应用
cpp
#ifdef __cplusplus
extern "C"{
#endif
....
}
#ifdef __cplusplus
}
#endif
__cplusplus是C++的预定义宏,表示当前开发环境是C++
C++为了支持重载,在编译生成的汇编代码中,会对函数名字进行一些处理(通常称为函数名字改编),如加入函数的参数类型或返回类型等,而在C语言中,只是简单地函数名字而已,如下所示:
cpp
int func(int a);
int func(double a);
C语言无法区分上面两个函数的不同,因为C编译器产生的函数名都是_func,而C++编译器产生的名字则可能是_func_Fi和_func_Fd,这样就很好的把函数区分开了。
作用:告诉C++编译器这段代码要按C标准编译,以尽可能地保持C++与C的兼容性。