C++进阶
1.内存的表达
1.1 计算机怎么存放整数
int整数
运用二进制存存储
例子:7的存储方式:0111,1111(原码),1000(反码),1001(补码)。第一个0和1表示符号
1.2 计算机怎么存放小数
- 定点数:小数点位置固定——>精度高
- float浮点数:小数点位置不固定,是浮动的——>更灵活、精度低、运算速度慢
运用IEEE 754标准存放:符号位(最高一位表示符号,0表示正数,1表示负数)+指数(8位)+尾数(23位)
1.3 什么是位运算
int 4B 32bit
short 2B 16bit
char 1B 8bit
- &与运算
- |或运算
- ^异或
- ~取反
- <<左移
- ‘>>右移’
1.4 大端字节序和小端字节序
字节序:大于一个字节类型的数据在内存中的存放顺序
例子:我们现在有一个整数是258。用16进制表示是0x0102,然后我们把这个整数拆分成两个字节,第一个字节为 0000 0001,第二个字节为 0000 0010。
如果在一个使用大端字节序的电脑上,这个整数会被这样存放:0000 0001 0000 0010
如果一个使用小端字节序的电脑上,这个整数的高字节就会存放在高地址上:0000 0010 0000 0001
大多数机器:小端字节序
I/O方面:大端字节序(网络字节序)
例子:要使用网络发送一个 int 类型的变量,要先把 int 转换成大端字节序,然后通过网络发送。
2. 指针探秘
2.1 指针的本质
本质:指向一个地址
地址——>内存:*
内存——>地址:&(取出变量的地址)
例子1:
//当p是一个指针时
#include<memory>
shared_ptr<int> p = make_shared<int>(42);//定义p为shared_ptr的智能指针
//p:表示这个指针变量指向的内存地址
//*p:表示这个指针指向的内存地址中存放的内容,就是42
//&p:表示p这个指针的地址
例子2:
//eg1:指针指向变量
int a = 2;
int * p = &a;//将a的地址取出来赋给指针变量p,指针p就指向了变量a
p = 1;
//eg2:指针指向数组
int arr[10];
int *p = arr;//指向数组第一个元素的位置
p = 12//对arr的第一个元素赋值为12
p+1 = 13//对arr的第二个元素赋值为13
//*p+1的含义:p的地址+类型大小,
//如p为int,则相当于原地址+4;如p为short,则相当于原地址+2
//*p++ 等价于 *p = *p + 1
//*p-- 同理
//p[1] 等价于 *(p+1)
//指针和结构体
struct S{
int a = 10;
float b = 20;
}
struct S s;
s.a = 12; //操作的句柄是普通的就用"."
s.b = 22;
struct S *p = &s;
p->a = 12; //操作的句柄是指针就用"->"
p->b = 22;
2.2 指针指向函数
向函数中传入另外一个函数——>函数指针
//eg1:
int main(int argc, char** argv)
{
//声明函数指针
int (* funcP)(int a, int b) = func1;
//调用函数
int ret = (* funcP)(5, 4);
}
//eg2:给函数加入一个指针参数
//作用:可以让不同使用该函数的人,将其所要插入的函数代码插入到dowork这个函数中。
int dowork(int a, int b, int(*callback)())
{
int c = a + b;
int ret = (*callback)();
...
}
2.3 空指针和野指针
- 野指针:无法确认指向的指针,或者指向一个无效地址的指针。——>十分危险
//容易引发野指针的情况:
int * p = (int *)malloc(2 * sizeof(int));
free(p);
free(p);
- 空指针:指针表示不指向任何一个地方
int * p = (int *)malloc(2 * sizeof(int));
free(p);
p = nullptr;//将指针置为空,意味着其不指向任何一个地方,这样就不会有问题了
free(p);
//练习1:编写一个函数,用来交换两个 int 变量的值
void swap(int * a, int * b)
{
int temp = *a;
*a = *b;
*b = temp;
}
2.4 函数返回指针
char * func()
{
char * p = nullptr;
return p;
}
什么情况下需要返回一个指针?返回指针时需要注意什么?
//错误代码,错误原因:arr是一个局部变量,func结束后就被销毁了,自然会出问题。
int * func()
{
int arr[] = {
1, 2, 3, 4};
return arr;
}
//解决办法:把内存分配到堆内存上
int * func()
{
int * arr = (int *)malloc(4 * sizeof(int));
return arr;
}
int main(int argc,char **argv)
{
int * p = func();
free(p); //调用完后要记得手动销毁!!
return 0;
}
2.5 解引用
返回内存地址中对应的对象
int a = 10;
int * p = &a;
cout<<*p<<endl;//输出a的值就是解引用操作
3. 类和对象
更加注重数据和动作的联系
- 声明一个类必须加";",而函数不用
3.1 分文件编程
可以把一个类写到两个文件中:
- .h或.hpp:头文件
- .c或.cpp:实现文件
如何Staff.h文件是定义,Staff.cpp是实现,想要在main函数中引用这个类就需要使用 #include “Staff.h” 将头文件引入进来。
#include "Staff.h"
int main(int argc,char **argv)
{
// 我们就这样实例化了三个员工
Staff st1;
Staff st2;
Staff st3;
return 0;
//将这三个'员工'分配到了栈上,可以把他们分配到堆内存上去。
}
3.2 new和delete
要将对象分配到堆上,需要使用两个关键字:new和delete。
- new:分配对象,返回一个指针
- delete:把这个指针指向的地址释放掉
#include "Staff.h"
int main(int argc,char **argv)
{
// 我们就这样实例化了三个员工
Staff * st1 = new Staff();
Staff * st2 = new Staff();
Staff * st3 = new Staff();
// 记得释放
delete st1;
delete st2;
delete st3;
return 0;
}
3.3 权限修饰符
private
class A
{
private:
int a;
int b;
}
int main(int argc,char **argv)
{
A a;
a.a = 15; // 会报错!!,因为成员变量是 private 的
return 0;
}
public
class A
{
public:
int a;
int b;
}
int main(int argc,char **argv)
{
A a;
a.a = 15; // 不会报错,因为成员变量是 public 的
return 0;
}
protected
用来指定保护成员。一般是允许在子类中访问。(后续学习类的继承后你就明白了)
3.4 成员函数
//头文件Staff.hpp中:
#include <string>
class Staff
{
public:
std::string name;
int age;
int PrintStaff();
};
//对应的.cpp文件中:
#include "Staff.hpp"
int Staff::PrintStaff()
{
printf("Name: %s\n", name.c_str());
printf("Age: %d\n", age);
return 0;
}
//在main函数中调用:
#include <stdio.h>
#include "Staff.hpp"
int main(int argc,char **argv)
{
Staff staff1;
staff1.PrintStaff();
return 0;
}
同样,因为上述函数写到了public下面,可以在外部对其进行调用,如果把他放到private下面,就不能被外部调用,只能被类中其他函数调用。
成员函数的重载
#include <string>