- 从初始化/赋值语句谈起
- 类型概述
- 类型详述
- 复合类型:从指针到引用
- 指针
- 引用
从初始化/赋值语句谈起
初始化与赋值语句:将一个值与某一个对象关联起来。初始化是声明一个新的变量,给一个初始值。赋值语句是改变变量的值,赋予一个新的值。
字面值:看一个程序,就知道它的值。
对象所表示的值:x = y。
等号左边是一个标识符,可以是常量、变量、引用、指针等。
初始化是在内存当中开辟一块空间,可能是在栈上开辟,也可能是在堆上开辟。
赋值的时候,就不需要开辟额外的空间了,因为初始化的时候已经开辟了。
#includeint main() { int x = 10; x = 20; int y = 30; x = y; int z = 5.14; std::cout << x << y << z << std::endl; }
5.14强制转化成5赋值给int类型的z。
类型是存在于c++内部的,c++是强类型的语言,在编译时有类型,编译完成,没有类型。为什么要把类型引入呢?几乎所有高级语言,都有类型的概念,123一个数,int和double解析是不同的,类型可以更好的描述程序,防止误用。
类型描述了4种信息:
- 存储所需的尺寸:
#includeint main() { int x = 10; char y = 'a'; std::cout << sizeof(x) << 'n'; std::cout << sizeof(int) << 'n'; std::cout << sizeof(y) << 'n'; std::cout << sizeof(char) << 'n'; }
4是4个字节,1是1个字节byte。
1个字节占8个bit。int就是32个bit,char是8个bit。一个bit一位。
- 取值空间:
#include#include int main() { std::cout << std::numeric_limits ::min() << std::endl; std::cout << std::numeric_limits ::max() << std::endl; std::cout << std::numeric_limits ::min() << std::endl; std::cout << std::numeric_limits ::max() << std::endl; }
int main()
{
unsigned int x = std::numeric_limits::max();
x += 1;
std::cout << x << std::endl;
}
x输出0。上溢出了。
最小值下溢出就变成最大值,最大值上溢出就变成最小值。
- 对其信息:
内存开辟一段连续的字节,是要与cpu进行交互的,
x如果存在7999,对于程序读取就会比较麻烦,先读取8000-8064,发现只有一部分x,再去读8000减64 - 7000. 类型就是让我们保存数据的时候,在内存中是对其信息的。对其信息让地址可以被整除。
struct Str
{
int x;
char y;
};
int main()
{
std::cout << sizeof(Str) << std::endl;
}
Str的size是8不是5,就是对其信息影响了。
- 可以执行的操作:
不同的类型可以进行不同的操作。
- 数值类型
内建:内部已经建立好的类型。
char一个字节,8位bit,256个数,通常表示ASCII编码。或者UTF-8的编码。char_16t是2个字节,char_32t是4个字节。UniCode编码。表示一些中文或者一些图案。
无符号只能取正数。不同的整数类型。
float是单精度,double是双精度,通常double比float尺寸宽一倍。
#include#include int main() { std::cout << sizeof(char) << std::endl; std::cout << sizeof(short) << std::endl; std::cout << sizeof(int) << std::endl; std::cout << sizeof(long) << std::endl; std::cout << sizeof(long long) << std::endl; std::cout << sizeof(float) << std::endl; std::cout << sizeof(double) << std::endl; std::cout << sizeof(long double) << std::endl; }
#include#include int main() { std::cout << std::numeric_limits ::min() << std::endl; std::cout << std::numeric_limits ::max() << std::endl; std::cout << std::numeric_limits ::max() << std::endl; std::cout << std::numeric_limits ::max() << std::endl; std::cout << std::numeric_limits ::max() << std::endl; std::cout << std::numeric_limits ::max() << std::endl; }
-
void
void一般放在函数的返回类型或者形参列表当中。 -
复杂类型
一般由基本类型组成产生(自己定义的类)。
或者基本类型变种产生(指针、引用、常量类型等)。
标准库引入的(vector,string),或者自定义的类型(Point类)。
c++没有说明char是有符号类型,还是一个无符号类型。但是可以通过unsigned强制定义。
int main()
{
char ch1;
unsigned char ch2;
signed ch3;
}
在系统当中,我们不会规定小端法和大端法,但是当涉及在2个硬件设备传输的过程中,我们就要考虑了。否则出错,
为什么要引入固定大小的类型,因为相同类型在不同设备之间可能是不同的字节,所以c++11规定了一些固定尺寸的类型,比如int32_t.(32个比特,4个字节)。
字面值表示一个具体的数值或者字符串的值。
注意c++中,如果要是使用字符串字面值,一定是双引号。
“Hello” char[6],是一个数组类型,包含了6个元素,5个字符,尾部是一个 (转义字符),表示字符串的结束。
指针我们一般不关心它的类型,主要是它的字面值。
-ddd是自定义的类型。
变量的内容是可以改变的量。
我们只能定义一个变量,另外的文件可以声明这个变量。通过extern前缀。
如果一个变量是在函数内部的默认初始化,那么这个值是一个任意值。如果是在全局或者一个线程上的默认初始化,会赋予初始值0。原因是函数不知道被调用多少次,程序每次调用函数都要想着去默认初始化这个值,对于效率大大降低。但是在全局中,变量只初始化一次,所以可以赋值一个0.
使用了extern,不要初始化。会出现重复定义的情况。
类型转换,有时有损,有时无损。
cpp20的一些方法,避免上面隐式转换的问题。
int main()
{
int val = 42;
int* p = &val;
}
&是取地址的意思。
int main()
{
int val = 42;
int y = 50;
int* p = &val;
p = &y; // 指针可以指向不同的对象
}
#includeint main() { int x = 10; char y = 'a'; std::cout << sizeof(x) << std::endl; // 4 std::cout << sizeof(y) << std::endl; // 1 int* p1 = &x; char* p2 = &y; std::cout << sizeof(p1) << std::endl; // 8 std::cout << sizeof(p2) << std::endl; // 8 }
指针p1和p2尺寸一样,都是8.指针里面保存的数据是变量的地址,是一个数值。这个数值是因为cpu是64位机,64位就是最大64条线程,处理2^64的数值,64个bit,等于8个字节。所以是8.
int main()
{
int x = 10;
std::cout << &x << std::endl; // 输出 0x7ffeec4946cc 地址
}
int main()
{
int x = 10;
int* p = &x; // 这里的*是一个声明,声明p是一个指针
int *p = &x; // 这种写法也是可以的
// 这里的*是一个解引用,取得p中存放的地址,按照这个地址去内存中去找,找到后,取出里面的数据
std::cout << *p << std::endl; // 输出 10,指针p指向的地址中的字面值
}
int main()
{
int x = 10;
int* p1 = &x;
char y = 'a';
char* p2 = &y;
std::cout << *p1 << ' ' << *p2 << std::endl; // 10 a
}
注意,我们的程序在读取p1和p2的时候,因为指针只是存放了首地址,所以我们就要根据指针的类型,去读取多少个连续的内存。所以指针定义时,必须要知道类型。
int main()
{
int* p1;
std::cout << p1 << std::endl; // 随机值:0x7ffee7bc36e8
}
nullptr是一个对象,这个对象的值是0,指向地址为0的指针。可以隐式转换成任意类型的指针。
#includevoid fun(int) { std::cout << "1n" << std::endl; } void fun(int*) { std::cout << "2n" << std::endl; } int main() { fun(nullptr); // 2 fun(0); // 1 }
指针和bool的隐式转换。
int main()
{
int x = 42;
int* p = &x;
p = p + 1; //这里的1,不是单纯的1,指针是int类型,加1,就是地址加4.指向下一个地址。
}
那我们什么时候用到指针的增加和减少呢?一般是在数组中。
int main()
{
int x = 42;
int* p = &x;
int* q = &x;
std::cout << (p == q) << std::endl; // 1
}
不要去用大于或者小于去比较指针。相等是指针指向了相同的对象,不等于说明指针指向了不同的对象。
关于void*,有时候我们不需要知道指针是什么类型,只要它是一个指针即可。这时候用void*。void可以转化成任意类型的指针,也可以由任意类型的指针转换到它。
但是用void本身丧失了很多功能。因为过于泛化了。
void fun(void* param) // void* 可以转换成任何类型的指针
{
}
int main()
{
int* r = nullptr;
char* k = nullptr;
fun(r);
fun(k);
}
通常,不要给void的指针作加减操作。
如果void指向数组的不同对象,我们可以进行大小判断。
为什么要引入指针的概念,指针是对对象的借鉴引用。借鉴引用的好处是减少程序传输所用的成本。对象的长度是不同的,但是指针的尺寸是相通的。
而且有时候Str不支持复制,这时候就要用到指针。
Str*是支持复制的。地址就是一个数据。相当于param和x同时指向一个地址。这样的调用函数,效率大大增高,复制成本低。但是指针读写成本高。解引用。2次内存访问。读写成本都比较高。
值传递
void fun(int param)
{
param += 1;
}
int main()
{
int x = 3;
fun(x);
// 传值
std::cout << x << std::endl; // 3,因为这里fun调用x,x拷贝给了param。param任何修改,都不会影响x。
}
指针传递
void fun(int* param)
{
*param += 1;
}
int main()
{
int x = 3;
int* p = &x;
fun(p);
// 传指针
std::cout << x << std::endl; // 4,因为这里fun调用p,p的地址拷贝给param。param和p同时指向x。
}
指针的问题,地址可能是非法的,所以需要使用引用。
引用int main()
{
int x = 3;
int& ref = x;
}
引用是对象的别名,不能绑定字面值。
int main()
{
int x = 3;
int& ref = x;
std::cout << ref << 'n'; // 3
ref += 1;
std::cout << x << 'n'; // 4
}
int main()
{
int x = 3;
int* ptr = &x;
int y = 0;
*ptr = y;
std::cout << x << 'n';
std::cout << *ptr << 'n';
ptr = &y;
std::cout << x << 'n';
std::cout << *ptr << 'n';
}
指针有2层含义,要么是*p,改变绑定对象的值。要么是p,改变地址。
但是引用不一样,引用只能改变对象的值。
如果要声明一个引用,必须要一个显式的初始值。
引用不会出现空指针,以及非法的情况。
在计算机中,没有对象的别名这个概念。在cpu没办法通过硬件找到别名的概念,cpp强制添加了引用的概念,看引用的实现原理,还是指针实现的。
int main()
{
int x = 3;
int* ptr = &x;
int*& ref1 = ptr; // ref1 是一个int指针的引用
int& ref2 = x; // ref2 是一个int的引用
}
有指针的引用,但是没有引用的引用。



