C++面向对象思考方式
遇到复杂问题,先从问题中找名词,然后确立这些名词哪些可以作为类,再根据问题需求确定类的属性和方法,确定类之间的关系。
面向对象强调在运行阶段进行决策,而不是编译阶段。
1. 原码,反码和补码
负数在计算机中以补码的形式存储
非负数在计算机中以**原码(原码 = 补码 = 反码)**的形式存储
1.1 正数
补码 = 原码 = 反码
1.2 负数
第一位为符号位,0代表正数,1代表负数
反码:原码的符号位不变,其他位按位取反
补码:反码再+1
1.3 补码的意义
1)统一0的编码
+0:00000000
-0:原码1000 0000 ->反码 1111 11111 ->补码1 0000 0000 因为只有8位,最高位要去掉,所以是0000 0000,所以不管是+0还是-0,补码都是0000 0000
2)将减法运算变为加法运算
没有补码 10-6
0000 1010
1000 0110
= 1001 0000 = -16 结果有问题
有补码
0000 1010
1111 1010
0000 0100 = 4 结果正确
2. 浮点数理解
种类-------符号位-------------指数位----------------尾数位----
float—第31位(占1bit)—第30-23位(占8bit)----第22-0位(占23bit)
double–第63位(占1bit)—第62-52位(占11bit)—第51-0位(占52bit)
float类型取值范围:
会有精度缺失问题
浮点型最终表示为1.b*2^c,b为尾数,c为指数
符号位:1位
指数位:8位, 实际指数转换为二进制时需要+127,因为存在正负问题
尾数位:23位
最大值:符号位为0 指数位 1111 1110 (因为全1有代表的特殊含义) 尾数位 11111111
则float类型最大值为:1.尾数 * 2^(254-127) ,最小值为负的最大值
float的精度和取值范围_AlbertS的博客-优快云博客_float精度
3.new和delete
3.1 普通变量内存申请与释放
// 堆区申请内存 初始值20
int *data = new int(20);
delete data; // 释放内存
3.2 数组内存申请与释放
int *arr = new int[10];// 数组有10个元素,已初始化好
// 释放数组加中括号
delete[] arr;
对空指针使用delete是安全的
静态联编:在程序编译时确定数组大小
动态联编:在程序运行时确定数组大小
4. 引用
4.1 给变量起别名
数据类型 &别名 = 原名
int a = 10;
int &b = a;
b = 20 则a也为20
4.2 注意事项
1)引用必须初始化
2)引用的指向不可修改
4.3 引用做函数参数
int func(int a)
int c;
func© 改变a的值并不会改变c 。
值传递:形参不能修饰实参
地址传递:指针传递,形参能修饰实参
**引用做函数参数:**简化指针修改实参,直接用引用来修饰实参
int swap(int &a, int &b)
int c, d;
swap(c, d); a是c的引用,b是d的引用
**引用传递:**形参也能修饰实参
4.4 引用做函数返回值
1) 引用不能返回局部变量
int& func()
{
int a = 10;
return a;
}
int &ref = func();
这样获得的值是非法的,因为函数结束时,变量的内存已经释放,返回的引用是一个悬空引用
2)函数返回值可以作为左值
int& func()
{
static int a = 10;
return a;
}
func = 1000; 则a = 1000;
4.5 引用的本质
const int* ref const 修饰的是int,优先往左修饰
int * const 修饰都是*
引用在C++内部实现是一个指针常量,一个const修饰的指针,指向是固定的
int &ref = a;
int const ref = &a; 相等。一个const类型的指针 指向int 型的变量*
void func(int &ref)
{
ref = 100;//发现是引用,自动转化为*ref = 100,解引用
}
4.6 常量引用(重要)
常量引用的形式:void func(const int & val)
不允许改变val的值。
使用场景:想使用引用传递,同时又不想对这些值进行修改。
相比于普通引用的优势:
1)普通引用
void fun1(int &val)
int a=1;
fun1(a); //允许
fun1(1);//不允许 引用的目的是能改变传递值,而传递临时值作为引用是没有意义的。
2)常量引用
void fun2(const int & val)
int a=1
func1(a); //允许
func1(1); //允许 常量应用不允许改变值,所以即使是临时变量C++也能接受了
常量引用允许使用临时值作为传递
相比常量引用,为什么不直接选择值传递:
1)减小时间和空间开销:
值传递时会产生临时对象的构造和析构,例如void func3(int a); func3(1); 本质上在函数内部会有int a= 1等步骤
2)引用传递可以避免截断问题
例如对于C++的多态特性来说
class Person {};
class Student : public class Person {};
void func(Person s) // 值传递时,只能创建Person对象,来进行传递。Person s; func(s);
void func2(Person & s) //引用传递时,可以传入子类对象,实现多态。 Student s; func2(s);
所以复杂的参数传递,最好选择常量引用进行传递。
常量引用总结:
1)避免不想修改的值被修改
2)相比普通引用,可以直接按临时值传递
3)相比值传递,能减小时间和空间开销
4)相比值传递,能避免截断问题。
基本数据类型,最好还是按值传递。
int &ref = 10;//不合法,引用必须引一块合法的内存
void func(const int &val)//防止val被误修改
当参数类型和引用类型不符时,可被转换为引用类型,将创建一个临时变量,并传递这个临时变量的引用,而因为临时变量不能用来初始化 非常量引用,所以必须是常量引用
func(const string & str1, const string & str2) 可以直接这样传递 func(str1, “ABCD”)
字符串被转换为string类型
数组名被解释为数组首元素的地址。
char arr[10];
arr 等价于&arr[0],代表是一个字节内存块的地址
所以arr + 1 偏移一个字节
&arr 则代表是10个字节内存块的地址, &arr + 1,代表是偏移10个字节
5.不同精度的数据转换
1、 在32位机上,int型和unsignedint型都是32位的(4个字节)。
2、 enum会跟据最大值来决定类型,一般来说为int型,如果超出int型所能表示的范围,则用比int型大的最小类型来表示(unsigned int, long 或者unsigned long)
3、 关于类型的大小。一般用所能表示的数据范围来比较类型的大小,如char型<unsigned char型<short型…在表达式中,一般都是由小的类型向大的类型转换(强制类型转换除外)
4、 所有比int型小的数据类型(包括char,signedchar,unsigned char,short,signed short,unsigned short)转换为int型。如果转换后的数据会超出int型所能表示的范围的话,则转换为unsigned int型;
5、 bool型转化为int型时,false转化为0,true转换为1;反过来所有的整数类型转化为bool时,0转化为false,其它非零值都转为true;6、 如果表达式中混有unsignedshort和int型时,如果int型数据可以表示所有的unsigned short型的话,则将unsigned short类型的数据转换为int型,否则,unsigned short类型及int型都转换为unsigned int类型;
7、 unsigned int 与long类型的转换规律同3;
8、 如果表达式中既有int 又有unsignedint,则所有的int数据都被转化为unsigned int类型;
9、 始终要牢记内存中的表示,至于printf或者强制类型转换都只不过是对数的不同解释。如将有符号低精度数转换为高精度时,符号位向上扩展,如果是无符号低精度转换为高精度时,高位补0即可。如下程序所示。与此对应的是,如果是高精度转换为低精度时,那么只会装载高精度的相应低位,无论该数为signed 或是unsigned。
示例
//1100 0100
uint16_t a = 0x00C4;
uint16_t aa = 0x0004;
char b = (char)a;
char bb = (char)aa;
uint8_t c = (uint8_t)b;
uint16_t d = (uint16_t)b;
uint16_t dd = (uint16_t)bb;
printf("a:%u, b:%d, c:%u, d:%u, dd:%u\n", a, b, c, d, dd);
//符号位为1 ,则向上扩展1 ,否则扩展0
//a:196, b:-60, c:196, d:65476, dd:4