如何选择类型?
1.当明确不可能为负值时,选用无符号类型
2.一般选用int,如果超过了int,就选择long long
3.在算术表达式中不要选用char 或 bool 类型,只有在存放字符或者布尔值时才引用
4.执行浮点数时用double,因为float通常精度不够,而单精度和双精度的计算代价相差无几。
含有无符号类型的表达式
把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。
如果表达式里既有无符号类型又有带符号类型,当带符号类型取值为负时会出现异常结果,这是因为带符号数会自动转换成无符号数。
比如
a*b = -1;
而 unsigned a=b=-1;
a*b = 4294967295;
类型转换
对象的类型定义了对象能包含的数据和能参与的运算
类型表示的范围决定了转换的过程:
1.浮点数赋给整数:仅保留小数点之前的内容
2.整数赋给浮点数:小数点后为0,若整数所占空间超过浮点类型的容量,可能损失精度
3.赋给无符号类型一个超出它表示范围的值时:结果是初始值对无符号类型表示数值总数取模后的余数
4.赋给带符号类型一个超出它范围的值时:结果是未定义的
新的类型转换符
1.const_cast
用来移除对象的常量性
一般用于指针或者引用,不用于一般对象
使用const_cast去除const限定的目的不是为了修改它的内容,通常是为了函数能够接受这个实际参数
const int val = 100;
//int n = const_cast<int>(val);//error
//int *p = &val; //error :必须是const
int *p = const_cast<int *>(&val);
*p = 200;
cout << p << endl;
//p确实指向了val,但不能改变val的值,有一个临时对象,操作的是临时对象的空间,常量是不能被更改的
cout << val << endl;
const int val2 = 200;
//int &q = val2;
int& q = const_cast<int&>(val);
2.static_cast
附:隐式转换 编译器可以自动完成的(一般来说是安全的)
显式转换(强制类型转换)
(1).编译器隐式执行的任何类型转换都可以有static_cast完成
(2).当较大的算术类型赋值给较小的类型时,可以用static_cast进行强制转换
(3).可以将void*指针转换为某一类型的指针
(4).可以将基类指针指向派生类指针
(5).无法将const转换为nonconst,只能用const_cast
int n = static_cast<int>(3.14);
cout << n << endl; //3
void *p = &n;
int *p1 = static_cast<int*>(p);
cout << *p1 << endl; //3
3.reinterpret_cast
通常为操作数的位模式提供较低层的重新解释,也就是说将数据以二进制存在形式的重新解释。
新式类型转换总结
这三种都可以用旧式类型转换替代
还有dynamic_cast<T>(expr)
:作为一个向下的类型安全转换,来进行运行时类型识别。以后再讨论)
类型转换规则
(1).尽可能避免使用强制转换
(2).如果无法避免,推荐使用新式类型转换
引用
1.一般在初始化对象时,初始值会被拷贝到新建的对象中,然而定义引用时,程序把引用和它的初始值绑定在一起,共享空间,而不是被初始值拷贝给引用
2.引用并非对象,它只是为一个已经存在的对象所起的另外一个名字,所以不能定义引用的引用
3.对引用的任何操作都是在与之绑定的对象上进行的
4.引用类型的初始值必须是一个对象,而且引用的类型要和与之绑定的对象类型严格匹配
5.引用没有独立的空间
6.引用一经初始化,不能重新指向其他变量
引用的用法
1.函数的参数(按引用传递)
2.引用做为返回值:可以将函数返回值放在赋值运算符的左边
注意:不能返回对局部变量的引用:局部变量在函数返回时被销毁了
/*对局部变量的引用的返回*/
int &add(int a,int b)
{
int sum;
sum = a + b;
return sum;
}
/*对全局变量的引用返回*/
int array[] = {0,1,2,3,4};
int &index(int i)
{
return array[i];
}
void swap(int &a,int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10,b = 20;
swap(a,b);
cout << "a=" << a << " ";
cout << "b=" << b << endl;
/*引用做为函数的返回值,在函数返回的时候初始化*/
index(3) = 100; //array(3)被初始化为a[3]
cout << array[3] <<endl;
int s = add(2,3);
int &n = add(4,5);
cout << "2+3 = " << s << endl; //error
cout << "4+5 = " << n << endl;
return 0;
}
指针与引用的区别
1.指针本身就是一个对象,允许对指针赋值和拷贝;引用只是原变量的一个别名
2.指针无须在定义时赋初值(最好初始化),还可以为空,引用必须初始化
3.一旦定义了引用,就无法再绑定到另外的对象,之后每次访问这个引用都是访问最初的那个对象;给指针赋值就是存放一个新的地址,从而指向一个新的对象
4.指针可以有多级;引用不是一个对象,不能定义引用的引用
5.引用访问一个变量是直接访问,而指针是间接访问
注:面对一条复杂的指针或引用的声明语句时,从右往左阅读有助于弄清楚它的真是含义,离变量名名最近的符号对变量的类型有最直接的影响。
注意:尽可能使用引用,不得已时使用指针。
new+delete
new:
1.分配内存空间 -- malloc
2.调用析构函数
delete:
1.调用析构函数
2.释放内存
//int *p1 = new int; //分配一个int 空间 4字节
int *p1 = new int(33); //圆括号()表示分配一个空间并初始化
cout << *p1 << endl; //p1 = 33
int *p2 = new int[10]; //[]分配连续的10个整数空间 40字节
重载
相同的作用域,如果两个函数名称相同,而参数不同,我们把它们称为重载。
函数重载的不同形式:参数不同
形参数量不同;
形参类型不同;
形参顺序不同;
形参数量和类型都不同。注意:如果返回类型不同而其余部分相同则是非法的重载!
1.C++为了支持重载,在编译时进行了名字改编,即函数名在编译时并不以原函数名出现
extern “C” void fun(int a) //加上extern “C”表示不进行名字改编,就和C语言一样
2.实现C和C++混合编程:
#indef _cpluscplus
extern "C"
{
#endif
……
#ifdef _cpluscplus
}
#endif
带默认形参值的函数
1.函数没有声明时,在函数定义时指定形参的默认值
2.如果未给函数指定实参,则用默认参数值
3.函数既有定义又有声明时,声明时指定后,定义后就不能再指定默认值
4.默认值定义必须遵守从右到左的顺序,如果某个形参没有默认值,则它左边的形参也不能有默认值
重载的函数中如果形参带有默认值时,可能会产生二义性,即可以调用不止一个函数,此时编译器会报错。
内联函数
(一)why?
当程序执行函数调用时,系统要建立栈空间,保护现场,传递参数以及控制程序执行的转移等等,这些工作需要系统时间和空间的开销。有些情况下,函数本身功能简单,代码很短,但使用频率却很高,程序频繁调用函数花费的时间很多,从而使得程序执行效率降低。所以,为了协调好效率和可读性之间的矛盾,定义内联函数。
一般函数进行调用时,要将程序执行权转到被调用函数中,然后再返回到调用它的函数中;而内联函数在调用时,是将调用表达式用内联函数体来替换。
内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换。
(二)内联函数和带参数宏的区别
1.内联函数会进行类型检查,而宏调用只是实参简单地替换形参
2.内联函数是在编译的时候、在调用的地方将代码展开,宏是在预处理时进行替换的
注:C++高层次编程,推荐用 const、enum 来代替宏常量 inline代替带参数的宏
(三)内联函数的注意事项
1.内联函数必须是和函数体声明在一起,只能先定义后使用,才有效。
2.如果内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。
3.递归函数不能定义为内联函数。
4.内联函数一般适合于不存在while和switch等复杂的结构且只有1~5条语句的小函数上,否则编译系统将该函数视为普通函数。
5.对内联函数不能进行异常的接口声明。
(四)用法
inline int fun(int a,int b)
wan an