1、逻辑型
也称布尔型,其取值为true(逻辑真)和false(逻辑假),存储字节数在不同编译系统中可能有所不同,VC++中为1个字节。
声明方式:
bool result;
result=true;
可以当作整数用(true一般为1,false为0)
把其它类型的值转换为布尔值时,非零值转换为true,零值转换为false
#include<iostream>
using namespace std;
int main()
{
bool result;
result = true;
cout<<result<<endl;
return 0;
}
把其他类型值转换为布尔值时,非零值转换为true,零值转换为false(比如把result = 100,系统会有警告:会有一个截断)
2、const
用const给常量起个名字(标识符),这个标识符就称为标识符常量;因为标识符常量的声明和使用形式很像变量,所以也称常变量
定义的一般形式:
const 数据类型 常量名=常量值;
数据类型 const 常量名=常量值;
例如:
const float PI=3.14159f;
注意事项:
常变量在定义时必须初始化;
常变量初始化之后,不允许再被赋值;
#include<iostream>
using namespace std;
int main(void)
{
//const int a; Error,这是一个常量 必须初始化
const int a = 100;
//a = 200; Error,常量不能重新被赋值
return 0;
}
const可以作用于指针变量
#include<iostream>
using namespace std;
int main()
{
int b = 100;
int a = 50;
const int *p;//*p所指向的内容是不变的 可以不必初始化
//等价于int const *p
p = &b;
//*p = 200;
cout<<*p<<endl;
int *const p2 = &b;//指针指向的方向是固定的 不能改变
*p2 = 200;//但是所指向的内容是可以改变的
cout<<*p2<<endl;
return 0;
}
const与define的区别:
1、const定义的常量有类型,而#define定义的没有类型,编译可以对前者进行类型安全检查,而后者仅仅只是做简单替换
2、const定义的常量在编译时分配内存,而#define定义的常量是在预编译时进行替换,不分配内存
3、作用域不同,const定义的常变量的作用域为该变量的作用域范围。而#define定义的常量作用域为它的定义点到程序结束(当然可以用undef结束)
注:1定义常量还可以用enum,尽量用const、enum替换#define定义常量。
enum用法:
enum box{pencil,pen};定义了枚举变量Box,变量内的元素称为枚举元素
定义:enum box box2; //box box2
或者在声明的时候直接定义 enum {pencin,pen}box,box2;
枚举变量中的枚举元素系统是按照常量来处理的,故叫枚举常量,他们是不能进行普通的算术赋值的,(pencil=1;)这样的写发是错误的,但是你可以在声明的时候进行赋值操作!
enum box{pencil=1,pen=2};
但是这里要特别注意的一点是,如果你不进行元素赋值操作那么元素将会被系统自动从0开始自动递增的进行赋值操作,说到自动赋值,如果你只定义了第一个那么系统将对下一个元素进行前一个元素的值加1操作,例如
enum box{pencil=3,pen};//这里pen就是4系统将自动进行pen=4的定义赋值操作!
#include <iostream>
using namespace std;
void main(void)
{
enum egg {a,b,c};
enum egg test; //在这里你可以简写成egg test;
test = c; //对枚举变量test进行赋予元素操作,这里之所以叫赋元素操作不叫赋值操作就是为了让大家明白枚举变量是
//不能直接赋予算数值的,例如(test=1;)这样的操作都是不被编译器所接受的,正确的方式是先进行强制类型转换例
//如(test = (enum egg) 0;)!
if (test==c)
{
cout <<"枚举变量判断:test枚举对应的枚举元素是c" << endl;
}
if (test==2)
{
cout <<"枚举变量判断:test枚举元素的值是2" << endl;
}
cout << a << "|" << b << "|" << test <<endl;
test = (enum egg) 0; //强制类型转换
cout << "枚举变量test值改变为:" << test <<endl;
}
2高层次编程 用const enum inline 替换#define
3在底层编程(比如说框架类的编程) define很灵活
#include<iostream>
using namespace std;
#define STR(a) #a
#define CAT(a,b) a##b
int main(void)
{
int xy = 100;
cout<<STR(ABCD)<<endl; //#ABCD<=>"ABCD"
cout<<CAT(x,y)<<endl; //x##y <=>xy
return 0;
}
#include<iostream>
using namespace std;
//Effective C++ 3rd的一个例子。
#define CALL_WITH_MAX(a,b) ((a) > (b) ? (a) : (b))
int main()
{
int a = 5;
int b = 0;
CALL_WITH_MAX(++a, b); //a被累加二次
cout<<a<<endl;
CALL_WITH_MAX(++a, b+10); //a被累加一次
cout<<a<<" "<<b<<endl;
}
//在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”
3、结构体对齐
编译器为每个“数据单元”按排在某个合适的位置上。
C、C++语言非常灵活,它允许你干涉“内存对齐”
为什么要对齐?性能原因:在对齐的地址上访问数据快。
如何对齐?第一个数据成员放在offset为0的位置
其它成员对齐至min(sizeof(member),#pragma pack所指定的值)的整数倍。
整个结构体也要对齐,结构体总大小对齐至各个成员中最大对齐数的整数倍。
#include<iostream>
using namespace std;
#include<stdio.h>//为了进行地址的正确输出
struct Test
{
char a;
double b;
};
//第一个成员与结构体变量的偏移量为0
//其他成员要对齐到某个数字(对齐数)的整数倍地址
//对齐数取编译器预设的一个对齐整数与该成员大小的较小值
int main(void)
{
Test test;
//&test = &test.a
char *p = (char *)&test;
printf("p = %p\n",p);
//cout<<p<<endl;
p = &test.a;
printf("p = %p\n",p);
//cout<<p<<endl;
cout<<sizeof(Test)<<endl;
return 0;
}
可以直接到编译器中去修改预设的对齐数
如果改为4 则对齐数为4 那么结构体的大小为多大呢?? 12
如果结构体多了char c 那么结构体大小为多大呢?结构体总大小为最大对齐数的整数倍 16
总结:1第一个成员与结构体变量的偏移量为0
2其他成员要对齐到某个数字(对齐数)的整数倍地址
3对齐数取编译器预设的一个对齐整数与该成员大小的较小值
4、域运算符
C++中增加的作用域标识符 ::
用于对与局部变量同名的全局变量进行访问
用于表示类的成员,这将在关于类的一节中详细说明
#include<iostream>
using namespace std;
int var = 100;
int main(void)
{
int var = 50;
cout<<var<<endl;
cout<<::var<<endl;//可以访问全局变量
return 0;
}
5、new运算符
new运算符可以用于创建堆空间
成功返回首地址 失败直接会抛出异常(所以没必要与NULL进行比较)
语法:
指针变量=new 数据类型;
指针变量=new 数据类型[长度n];
例如:
int *p; p=new int;
char *pStr=new char[50];
delete运算符 可以用于释放堆空间
语法:
delete 指针变量;
delete [] 指针变量;(如果没有[] 结果是不确定的)
例如:
delete p;
delete [] pStr;
#include<iostream>
using namespace std;
int main(void)
{
int *p = new int; //分配一个整数空间 4字节
//可以进行初始化 int *p = new int(33); 如果是[]是分配多个空间
/*
如果是char *p = new char;
打印的话要用printf("p = %p\n",p);
*/
cout<<p<<endl;
int *p2 = new int[10];//分配连续的10个整数空间 40字节
delete p;
delete []p2;
return 0;
}
new在类中的应用:
new一个新对象的作用有:1、 内存分配(operator new) 2 、调用构造函数
delete释放一个对象:1、调用析构函数 2、释放内存(operator delete)
new实际有3种用法:
new operator 分配内存+调用构造函数
operator new 只分配内存
placement new 不分配内存、仅仅只是调用了拷贝构造函数 (后面详细进行解释)
6、重载
相同的作用域,如果两个函数名称相同,而参数不同,我们把它们称为重载overload
函数重载又称为函数的多态性 (这是一种静态的多态 在编译时刻就确定函数了 而动态是在运行时才确定的,称为静态联编、动态联编)
函数重载不同形式:1、形参数量不同 2、形参类型不同 3、形参的顺序不同 4、形参数量和形参类型都不同
调用重载函数时,编译器通过检查实际参数的个数、类型和顺序来确定相应的被调用函数
#include<iostream>
using namespace std;
//类型不同 也可以构成重载
void fun(int a,int b)
{
cout<<"int fun"<<endl;
}
void fun(double a,double b)
{
cout<<"double fun"<<endl;
}
int main(void)
{
fun(3,4);
fun(3.3,4.4);
return 0;
}
合法的重载例子:
int abs(int i);
long abs(long l);
double abs(double d);
非法的重载例子:
int abs(int i);
long abs(int i);
void abs(int i);
//如果返回类型不同而函数名相同、形参也相同,则是不合法的,编译器会报"语法错误"。重载的时候编译器是不会检查返回值的
其实重载的实质是 name managling
这里把它翻译为名字改编。
C++为了支持重载,需要进行name managling
#include<iostream>
using namespace std;
//类型不同 也可以构成重载
void fun(int a,int b)
{
cout<<"int fun"<<endl;
}
void fun(double a,double b)
{
cout<<"double fun"<<endl;
}
void fun(int a);
int main(void)
{
fun(3,4);
fun(3.3,4.4);
fun(3);
return 0;
}
此时编译器会报错, 无法解析的外部符号 "void __cdecl fun(int,int)" (?fun@@YAXHH@Z),该符号在函数 _main 中被引用
7、extern C
#include<iostream>
using namespace std;
//类型不同 也可以构成重载
void fun(int a,int b)
{
cout<<"int fun"<<endl;
}
void fun(double a,double b)
{
cout<<"double fun"<<endl;
}
//extern "c"表示不进行名字改编
extern "C" void fun(int a)
{
cout<<"xxxxxx"<<endl;
}
extern "C" void fun(double a)
{
cout<<"yyyyy"<<endl;
}
int main(void)
{
fun(3,4);
fun(3.3,4.4);
fun(3);
return 0;
}
此时会报错error C2733: second C linkage of overloaded function 'fun' not allowed
因为C是不支持两个函数同名的
为了更加的通用:
#ifdef __cpluscplus
extern "C"
{
#endif
void fun(int a);
void fun2(double a);
#ifdef __cpluscplus
}
#endif
//就是在extern "C"{ 与 }之间插入判断
8、带默认参数的函数
函数声明或者定义的时候,可以给形参赋一些默认值
调用函数时,若没有给出实参,则按指定的默认值进行工作
#include<iostream>
using namespace std;
//默认参数必须是从右往左赋值
int fun(int a,int b = 5)
{
return a + b;
}
int main(void)
{
cout<<fun(3)<<endl;
return 0;
}
函数没有声明时,在函数定义中指定形参的默认值
函数既有定义又有声明时,声明时指定后,定义后就不能再指定默认值
#include<iostream>
using namespace std;
int fun(int a,int b = 5)
{
return a + b;
}
//这个是已经声明的函数
int fun(int a,int b,int c = 6);
int main(void)
{
cout<<fun(3)<<endl;
cout<<fun(3,4)<<endl;
return 0;
}
//以下是定义 不需要给定默认值了
int fun(int a,int b,int c)
{
return a + b + c;
}
形参如果有默认值,那么函数容易产生二义性
int add(int x=5, int y=6);
int add(int x=5, int y=6, int z=7);
int main() {
int sum;
sum= add(10,20);
return 0;
}
9、引用
而引用仅仅是变量的别名 引用没有自己的空间
引用要与它所引用的变量共享空间
对引用所做的改变实际上是对它所引用的变量的改变
定义引用的一般格式:
类型 &引用名 = 变量名;
例如:int a=1;
int &b=a;
// b是a的别名,因此a和b是同一个单元
注意:定义引用时一定要初始化,指明该引用变量是谁的别名
在实际应用中,引用一般用作参数传递与返回值
引用一经初始化 不能重新指向其它变量 如下例 仅仅是将val2赋值给erfval 不像指针一样重新指向
#include<iostream>
using namespace std;
int main(void)
{
int val = 100;
int &refval = val;
refval = 200;
cout<<val<<endl;
int val2 = 500;
refval = val2;//不代表将refval引用至val2这个变量 仅仅只是代表将val2赋值给refval
cout<<val<<endl;
return 0;
}
const引用是指向const对象的引用
const int ival = 1024;
const int& refVal = ival; //ok:both reference and object are const
int &ref2 = ival; //error:nonconst reference to a const object
注:const引用可以指向普通变量 但是值不能够更改
int val2 = 1024;
const int &ref3 = val2;//这是允许的
按引用传递
引用传递方式是在函数定义时在形参前面加上引用运算符"&"
例如:swap(int &a,int &b);
按值传递方式容易理解,但形参值的改变不能对实参产生影响
地址传递方式通过形参的改变使相应的实参改变,但程序容易产生错误且难以阅读
引用作为参数对形参的任何操作都能改变相应的实参的数据,又使函数调用显得方便、自然
引用作为返回值
引用的另一个作用是用于返回引用的函数
函数返回引用的一个主要目的是可以将函数放在赋值运算符的左边。
(赋值运算符一般是放在右边的)
#include<iostream>
using namespace std;
//引用作为函数的返回值
int a[] = {0,1,2,3,4};
int &index(int i)
{
return a[i];
}
int main(void)
{
index(3) = 100; //引用作为函数返回值,使得函数可以放在赋值运算符左边
cout<<a[3]<<endl;
return 0;
}
不能返回对局部变量的引用。
因为引用在函数返回的时候进行初始化,如果是一个局部变量函数返回时,局部变量已经不存在了,所以这是不行的
#include<iostream>
using namespace std;
int &add(int a,int b)
{
int sum;
sum = a+b;
return sum;
}
int main(void)
{
//竟然会影响到n2的值 而不会影响n的值
//因为n是个变量,函数返回的时候能够捕捉到
//而n2是引用,没有自己独立的地址空间,所以n2依赖于它所引用的变量
//如果n2所引用的变量的生命期结束了,也就是说n2是一个无效的引用,那么n2的值将是不确定的。
//第一次成功是因为内容还没有被覆盖 而接下来被n所覆盖了 所以接下来输出的值是不确定的
int n = add(3,4);
int & n2 = add(5,6);
//cout<<"n2 = "<<n2<<endl;
cout<<"n = "<<n<<endl;
cout<<"n2 = "<<n2<<endl;
}
引用与指针区别
1、引用访问一个变量是直接访问,而指针是间接访问。
2、引用是一个变量的别名,本身不单独分配自己的内存空间,而指针有自己的内存空间。
3、引用一经初始化不能再引用其它变量,而指针可以。、
4、尽可能使用引用,不得已时使用指针。
值传递 实参要初始化形参要分配空间,将实参内容拷贝到形参
引用传递 实参初始化形参时不要分配空间
指针传递 本质是传递,实参初始化形参的时候,也是要分配空间的,分配的是4字节的空间(32位系统) 如果我们要修改指针的地址,单纯用指针传递是不行的,只能用** 或者&&
所以要尽可能使用引用 不得已使用指针
10、内联函数
//在编译时候已经展开了 让编译器做了 而不是程序员来做
inline intmax(int a, int b)
{
return a > b ? a : b;
}
#define MAX(a, b) (a) > (b) ?(a) : (b)
//本质都是展开的 并不涉及函数调用
3、在C++中建议采用inline函数来替换带参数的宏
总结 :常量 用const 带参数的宏(类似于函数调用) inline
11、类型转换
(T)expr
T(expr)
新式转型
const_cast<T>(expr)
static_cast<T>(expr)
reinterpret_cast<T>(expr)
dynamic_cast<T>(expr)
执行“安全向下”转型操作,也就是说支持运行时识别指针或所指向的对象,这是唯一个无法用旧式语来进行的转型操作。
用来移除对象的常量性(castaway the constness)
const_cast一般用于指针或者引用
使用const_cast去除const限定的目的不是为了修改它的内容
使用const_cast去除const限定,通常是为了函数能够接受这个实际参数
#include<iostream>
using namespace std;
int main(void)
{
const int val = 100;
int n = const_cast<int>(val);
return 0;
}
//因为没有任何意义 所以编译器不让通过
//const_cast一般来说 用指针或者引用
#include<iostream>
using namespace std;
void fun(int &val)
{
cout<<"fun "<<val<<endl;
}
int main(void)
{
const int val = 100;
//int *p = &val; Error,无法从const int*转换为int *
int *p = const_cast<int *>(&val);
//
*p = 200;
//并没有发生改变 可能生成了一个临时对象 操作的是临时变量的空间
//常量一定是不能更改的
//注:整型的地址应该是可以打印的
cout<<&val<<endl;
cout<<p<<endl;
cout<<"val = "<<val<<endl;
//接下来就是引用!
const int val2 = 200;
//int &refval2 = val2; Error
int &refval2 = const_cast<int&>(val2);
refval2 = 300;
cout<<"val2 = "<<val2<<endl; //不会改变常量val2的值,也是生成一个临时空间
fun(const_cast<int &>(val2));
}
当一个较大的算术类型赋值给较小的类型时,可以用static_cast进行强制转换。
可以将void*指针转换为某一类型的指针
可以将基类指针转成派生类指针
无法将const转化为nonconst,这个只有const_cast才可以办得到
#include<iostream>
using namespace std;
int main(void)
{
int n = static_cast<int>(3.14);
cout<<"n = "<<n<<endl;
void *p = &n;
int *p2 = static_cast<int *>(p);
}
注:
隐式转换:编译器可以自动完成的转换(一般来说安全的)
int a;short b;a = b;(精度低的给高的 一般不会出错)
显示转换:也称为强制转换,精度高的给低的,必须用显示转换