目录
布尔类型
C++在C语言的基本类型系统之上增加了bool,C++中的bool可取的值只有true和false,理论上bool只占用一个字节
bool类型只有true(非0)和false(0)两个值,true代表真值,编译器内部用1来表示;false代表非真值,编译器内部用0来表示
C++编译器会将非0值转换为true,0值转换为false
布尔类型是C++中的基本数据类型,可以定义bool类型的全局变量,可以定义bool类型的常量,可以定义bool类型的指针,可以定义bool类型的数组,......
#include <stdio.h>
int main(int argc, char *argv[])
{
bool b = false;
int a = b;
printf("sizeof(b) = %d\n", sizeof(b)); // 1
printf("b = %d, a = %d\n", b, a); // 0 0
b = 3;
a = b;
printf("b = %d, a = %d\n", b, a); // 1 1
b = -5;
a = b;
printf("b = %d, a = %d\n", b, a); // 1 1
a = 10;
b = a;
printf("a = %d, b = %d\n", a, b); // 10 1
a = 0;
b = a;
printf("a = %d, b = %d\n", a, b); // 0 0
return 0;
}
三目运算符
C语言中的三目运算符返回的是变量值,不能作为左值使用
C++中的三目运算符可直接返回变量本身,既可作为右值使用,又可作为左值使用,若三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用。
C++中当三目运算符的可能返回值都是变量时,返回的是变量引用,当三目运算符的可能返回值中有常量时,返回的是值
int a = 1;
int b = 2;
(a < b ? a : b) = 3; // gcc error , g++ ok
( a < b ? 1 : b ) = 4; // gcc error , g++ error 不能作为左值
printf("a = %d,b = %d\n", a, b);
C++中的引用
引用:可以看作一个已定义变量的别名,引用的语法:Type& name = var; name为var的别名,操作name就是操作var
注意:普通引用在定义时必须用同类型的变量进行初始化 ,否者出错
引用的意义:引用作为变量别名而存在,因此在一些场合可以代替指针,引用相对于指针来说具有更好的可读性和实用性
void swap(int& a, int& b) // 函数中的引用形参不需要进行初始化!
{
int t = a;
a = b;
b = t;
}
void swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
const引用:在C++中可以声明const引用 ,const引用让变量拥有只读属性 :const Type& name = var
const引用的类型与初始化变量的类型 如果相同:初始化变量成为只读变量 ,如果不同:生成—个新的只读变量
int main()
{
/*在编译期间不能直接确定初始值的const标识符,都被作为只读变量处理。*/
/*const只是告诉编译器, const标识符不能出现在赋值符号左边*/
int a = 4;
const int& b = a; // 只读变量
int* p = (int*)&b;
//b = 5; // Error
*p = 5; // Ok
a = 10; // Ok
const int& c = 1; // 使用常量对const引用进行初始化时
int* pc = (int*)&c; // C++编译器会为常量值分配空间,并将引用名作为这段空间的别名,生成一个只读变量
//c = 10; // Error
*p = 10; // Ok
const int i = 10; // 进入符号表
const int& d = i; // 直接使用i的内存空间
cout << (&i == &d) << endl; // 1
return 0;
}
思考: 引用有自己的存储空间吗?
// 引用的思考 test.cpp
#include <stdio.h>
struct TRef
{
char& r;
};
int main(int argc, char *argv[])
{
char c = 'c';
char& rc = c;
TRef ref = { c };
printf("sizeof(char&) = %d\n", sizeof(char&)); // 1
printf("sizeof(rc) = %d\n", sizeof(rc)); // 1
printf("sizeof(TRef) = %d\n", sizeof(TRef)); // 4 ???
printf("sizeof(ref.r) = %d\n", sizeof(ref.r)); // 1
return 0;
}
引用在C++中的内部实现是一个指针常量
void f(int& a)
{
a = 5;
}
// 两者等价
void f(int* const a)
{
*a = 5;
}
C++编译器在编译过程中用指针常量作为引用的内部实现,因此引用所占用的空间大小与指针相同。
从使用的角度,引用只是一个别名,C++为了实用性而隐藏了引用的存储空间这—细节。
#include <stdio.h>
struct TRef
{
char* before;
char& ref;
char* after;
};
int main(int argc, char* argv[])
{
char a = 'a';
char& b = a;
char c = 'c';
TRef r = { &a, b, &c };
printf("sizeof(r) = %d\n", sizeof(r)); // 12
printf("sizeof(r.before) = %d\n", sizeof(r.before)); // 4
printf("sizeof(r.after) = %d\n", sizeof(r.after)); // 4
printf("&a = %p\n", &a);
printf("&c = %p\n", &c);
printf("&r.before = %p\n", &r.before);
printf("&r.after = %p\n", &r.after);
return 0;
}
VS2015
C++只是隐藏了b是指针的事实
#include <iostream>
using namespace std;
int main()
{
int x = 1;
int y = 2;
int &b = x;
cout << &x << endl;
cout << &y << endl;
cout << hex << (&y - 1) << endl;
cout << hex << *(&y - 1) << endl;
return 0;
}
gcc version 4.4.5(Ubuntu 10.10)
栈的生长方向自上向下的,通过&y-1间接获得b的地址,*(&y-1) 即指针保存的x的地址(我的VS2015局部变量间总是有8字节的间隙)
可以猜测*(&x - 2)值为bfc02c3c
函数返回引用 :如果返回局部的栈中的变量的引用就相等于返回一个失效的地址,使用这段内存是非法的
C++不支持引用数组
#include <stdio.h>
int a = 1;
struct SV
{
int& x;
int& y;
int& z;
};
int main()
{
int b = 2;
int* pc = new int(3);
SV sv = {a, b, *pc};
printf("&sv.x = %p\n", &sv.x);
printf("&sv.y = %p\n", &sv.y);
printf("&sv.z = %p\n", &sv.z);
//int& array[] = {a, b, *pc}; // &array[1] - &array[0] = ? Expected ==> 4
/*error: declaration of ‘array’ as array of references*/
delete pc;
return 0;
}
对比地址发现为了兼容C语言所有特性,放弃引用数组,所以C++不支持引用数组
小结
指针是—个变量 , 值为一个内存地址,不需要初始化,可以保存不同的地址 ,通过指针可以访问对应内存地址中的值 ,指针可以被const修饰成为常量或者只读变量
引用只是—个变量的新名字 ,对引用的操作(赎值,取地址等)都会传递到代表的变量上,const引用使其代表的变量具有只读属性 ,引用必须在定义时初始化,之后无法代表其它变量
C++中的引用旨在大多数的情况下代替指针
-功能性:可以满足多数需要使用指针的场合
-安全性:可以避开由于指针操作不当而带来的内存错误
-操作性:简单易用,又不失功能强大
从使用C++语言的角度来看 :引用与指针没有任何的关系 ,引用是变量的新名字,操作引用就是操作对应的变量
从C++编译器的角度来看 :为了支持新概念“引用”必须要—个有效的解决方案 ,在编译器内部,使用指针常量来实现“引用” ,因此“引用”在定义时必须初始化
在工程项目开发中
-当进行C++编程时,直接站在使用的角度看待引用,与指针毫无关系,引用就是变量的别名
-当对C++代码进行调试分析时,—些特殊情况,可以考虑站在 C++编译器的角度看待引用
新型类型转换
C方式的强制类型转换 : (Type) (Expression) 、 Type (Expression)
C方式强制类型转换存在的问题
-过于粗暴,任意类型之间都可以进行转换,编译器很难判断其正确性
-难于定位 ,在源码中无法快速定位所有使用强制类型转换的语句
// 粗暴的类型转换 test.cpp
#include <stdio.h>
typedef void(PF)(int);
struct Point
{
int x;
int y;
};
int main()
{
int v = 0x12345;
PF* pf = (PF*)v;
char c = char(v);
Point* p = (Point*)v;
pf(5);
printf("p->x = %d\n", p->x);
printf("p->y = %d\n", p->y);
// 居然能编译通过
return 0;
}
C++将强制类型转换分为4种不同的类型
static_cast:用于基本类型间的转换 ,不能用于基本类型指针间的转换 ,用于有继承关系类对象之间的转换和类指针之间的转换
const_cast:用于去除变量的只读属性 ,强制转换的目标类型必须是指针或引用
reinterpret_cast:用于指针类型间的强制转换 ,用于整数和指针类型间的强制转换
dynamic_cast:用于有继承关系的类指针间的转换 ,用于有交叉关系的类指针间的转换 ,具有类型检查的功能, 需要虚函数的支持
dynamic_cast要求相关的类中必须有虚函数 ,用于有直接或者间接继承关系的指针(引用)之间
-指针: 转换成功:得到目标类型的指针 ,转换失败:得到一个空指针
-引用: 转换成功:得到目标类型的引用 ,转换失败:得到一个异常操作信息
编译器会检查dynamic_cast的使用是否正确 ,类型转换的结果只可能在运行阶段才能得到
// 新式类型转化初探 test.cpp
#include <stdio.h>
void static_cast_demo()
{
int i = 0x12345;
char c = 'c';
int* pi = &i;
char* pc = &c;
c = static_cast<char>(i);
// pc = static_cast<char*>(pi); // error
}
void const_cast_demo()
{
const int& j = 1;
int& k = const_cast<int&>(j);
const int x = 2;
int& y = const_cast<int&>(x);
// int z = const_cast<int>(x); // error
k = 5;
printf("k = %d\n", k);
printf("j = %d\n", j);
y = 8;
printf("x = %d\n", x);
printf("y = %d\n", y);
printf("&x = %p\n", &x);
printf("&y = %p\n", &y);
}
void reinterpret_cast_demo()
{
int i = 0;
char c = 'c';
int* pi = &i;
char* pc = &c;
pc = reinterpret_cast<char*>(pi);
pi = reinterpret_cast<int*>(pc);
pi = reinterpret_cast<int*>(i);
// c = reinterpret_cast<char>(i); // error
}
void dynamic_cast_demo()
{
int i = 0;
int* pi = &i;
// char* pc = dynamic_cast<char*>(pi); // error
}
int main()
{
static_cast_demo();
const_cast_demo();
reinterpret_cast_demo();
dynamic_cast_demo();
return 0;
}
// dynamic_cast的使用 test.cpp
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
cout << "Base::Base()" << endl;
}
virtual ~Base()
{
cout << "Base::~Base()" << endl;
}
};
class Derived : public Base
{
};
int main()
{
Base* p = new Base;
Derived* pd = dynamic_cast<Derived*>(p); //编译成功,转换失败返回0
if( pd != NULL )
{
cout << "pd = " << pd << endl;
}
else
{
cout << "Cast error!" << endl;
}
Base* p1 = new Derived();
Derived* pd1 = dynamic_cast<Derived*>(p1);//转换成功
if( pd1 != NULL )
{
cout << "pd1 = " << pd1 << endl;
}
else
{
cout << "Cast error!" << endl;
}
delete p;
delete p1;
return 0;
}
友元
友元是C++中的—种关系 ,友元关系发生在函数与类之间或者类与类之间 ,友元关系是单项的,不能传递
在类中以friend关键字声明友元 ,类的友元可以是其它类或者具体函数
友元不是类的—部分 ,友元不受类中访问级别的限制 ,友元可以直接访问具体类的所有成员
#include <stdio.h>
#include <math.h>
class Point
{
double x;
double y;
public:
Point(double x, double y)
{
this->x = x;
this->y = y;
}
double getX()
{
return x;
}
double getY()
{
return y;
}
friend double func(Point& p1, Point& p2); // 在类中用friend关键字对函数或类进行声明
};
double func(Point& p1, Point& p2)
{
double ret = 0;
ret = (p2.y - p1.y) * (p2.y - p1.y) +
(p2.x - p1.x) * (p2.x - p1.x); // 友元不受类中访问级别的限制
ret = sqrt(ret);
return ret;
}
int main()
{
Point p1(1, 2);
Point p2(10, 20);
printf("p1(%f, %f)\n", p1.getX(), p1.getY());
printf("p2(%f, %f)\n", p2.getX(), p2.getY());
printf("|(p1, p2)| = %f\n", func(p1, p2));
return 0;
}
友元关系不具备传递性 ,类的友元可以是其它类的成员函数
类的友元可以是某个完整的类 ,该类所有的成员函数都是友元
B类是C类的友元,A类是B类的友元,不代表A类是C类的友元
#include <stdio.h>
class ClassC
{
const char* n;
public:
ClassC(const char* n)
{
this->n = n;
}
friend class ClassB; //类ClassB是ClassC的友元,ClassB的所有成员函数都是ClassC的友元
};
class ClassB
{
const char* n;
public:
ClassB(const char* n)
{
this->n = n;
}
void getClassCName(ClassC& c)
{
printf("c.n = %s\n", c.n); //可以直接访问ClassC对象的私有成员
}
friend class ClassA; //ClassA是ClassB的友元
};
class ClassA
{
const char* n;
public:
ClassA(const char* n)
{
this->n = n;
}
void getClassBName(ClassB& b)
{
printf("b.n = %s\n", b.n); //可以直接访问ClassB对象的私有成员
}
/*
void getClassCName(ClassC& c)
{
printf("c.n = %s\n", c.n);//error,A是B友元,B是C友元,但A不是C友元
}
*/
};
int main()
{
ClassA A("A");
ClassB B("B");
ClassC C("C");
A.getClassBName(B);
B.getClassCName(C);
return 0;
}
友元是为了兼顾C语言的高效而诞生的 ,友元直接破坏了面向对象的封装性 ,友元在实际产品中的高效是得不偿失的 ,友元在现代软件工程中已经逐渐被遗弃