数据类型
目录
基本数据类型
变量所占大小
| | X64 | X86 |
| char | 1字节 | 1字节 |
| short | 2字节 | 2字节 |
| int | 4字节 | 4字节 |
| long | 8字节 | 4字节 |
| long long | 8字节 | 8字节 |
| float | 4字节 | 4字节 |
| double | 8字节 | 8字节 |
long double | 通常16字节 | 通常8字节 |
---|
char
分为三类型:
1.char:标准类型
2.unsigned char:无符号char(0~255)
3.signed char:有符号char(-128~127)
构造类型
数组
1.一维数组
字符串/字符数组
cpp
char arr1[] = "hello"; 字符串:结尾有'\0'终止符,arr1占6字节
char arr2[] = {'H', 'e', 'l', 'l', 'o'}; 字符数组:结尾无'\0',arr2占5字节
'\0'影响printf等输出,输出结果为'\0'之前
字符串的申明方式
1.静态分配,编译时在栈上分配好内存
2.字符串常量
3.动态分配内存(malloc和new)
4.自动分配,在局部函数中
操作函数
cpp
size_t strlen(const char *str) 返回字符串长度,不包含'\0'
char *strcpy(char *dest, char *src) 把src复制给dest
char *strncpy(char *dest, const char *src, size_t n) 把src复制给dest前n个字符
char *strcat(char *dest, const char *src) 把src添加到dest的结尾
char *strncat(char *dest, const char *src, size_t n) 把src前n个字符添加到dest结尾
int strcmp(const char *str1, const char *str2) 比较字符串是否相同,成功返回0,失败返回不同个数
int strncmp(const char *str1, const char *str2, size_t n) 比较前n个字符串,返回与strcmp相同
char *strchr(const char *str, int c) 返回c第一次在str出现的地址
char *strrchr(const char *str, int c) 返回c最后一次在str出现的地址
char *strstr(const char *haystack, const char *needle) 返回needle在haystack第一次出现的地址
地址
cpp
char arr[10] = "Hello";
arr与&arr[0]所表示是一样的
&arr是整个数组的地址,类型是char (*)[10],表示10个指向char元素的指针
指针数组与数组指针
cpp
指针数组,包含3个char的指针
char *ptr_arry[3]
数组指针,指向一个包含3个char的数组
char (*arry_ptr)[3]
2.二维数组
初始化
cpp
1.没什么好说的
int array[3][4];
2.编译器自动分配
int array[][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
3.分配动态内存
int rows = 3;
int cols = 4;
int **array = (int **)malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
array[i] = (int *)malloc(cols * sizeof(int));
}
4.使用stl容器
int rows = 3;
int cols = 4;
std::vector<std::vector<int>> array(rows, std::vector<int>(cols));
地址
&a[1],&a[0]==a;一维数组地址
&a[0][1],&a[0][0] == a[0];元素地址
&a;二维数组地址
结构体
声明
cpp
1.默认声明
struct Point {
int x;
int y;
};
C++的结构体class,为兼容c,保留了struct
2.定义并声明实例
struct Point {
int x;
int y;
} p1, p2;
3.结构体别名
typedef struct {
int x;
int y;
} Point;
Point p1;
C++默认声明,创建实例时不需要声明结构体struct或class
结构体大小
cpp
struct Example {
char a; // 1 byte
int b; // 4 bytes
char c; // 1 byte
};
以上面为例,变量存储地址为自身所占字节大小的倍数,设从0x00开始,char占1个字节,地址在0x00,b占4字节,因为地址要是自身字节大小的整数倍,所以地址在0x04;c占1个字节,地址在0x08,所以Example结构体的大小为9字节。
其中,c特有的位域,变量+:+位数,用于修改变量大小,节省内存空间。计算方式为同类型变量合并,不足该变量字节大小的部分自动填充,不同类型的变量按结构体的计算方式存入地址。
链表
单链表:简单,节点只包含指向下一个节点的指针。
双向链表:支持双向遍历,节点包含指向前一个和下一个节点的指针。
循环链表:尾节点指向头节点,形成一个环,适合需要循环访问的情况。
C++中class与struct区别
(1)相同点
都能拥有成员函数,公有和私有部分;class可以实现的struct也可以实现。
(2)不同点
struct默认公有,class默认私有;struct默认公有继承,class默认私有继承。
(3)C++的struct与C的区别
C语言中struct是自定义数据类型;C++中是抽象数据类型,支持成员函数的定义(C++中可以继承和实现多态)
C语言中struct没有访问权限设置,成员只能是变量,但可以存入函数地址,数据不能被隐藏。
C++设置了访问权限,功能与class一样,默认是public访问。
C语言声明实例时必须在前面加struct,除非定义结构体时使用typedef。C++不需要,结构体struct在C++中被当作特例。
联合体
联合体声明
cpp
union Data {
int i;
float f;
char str[20];
};
联合体大小计算
联合体的大小是其最大的数据成员大小的整数倍再满足编译器的内存对齐要求的最小倍数。以上面为例,联合体最大成员时char str[20],则联合体大小为20字节,若内存对齐要求为4字节,则不变;若为8字节,则填充4字节为24字节。
判断大小端
cpp
#include <iostream>
union data{
int a;
char b;
}
void main()
{
data udata;
udata.a = 0x12345678;
if(udata.b == 0x78)
printf("Little-Endian\n");
else if(udata.b == 0x12)
printf("Big-Endian\n");
else
printf("Unkonwn Endian\n");
}
指针
内存的申请与释放
C++中的new
cpp
分配内存,构造一个整数
int* ptr = new int;
分配内存,构造函数参数进行构造
MyClass* ptr = new MyClass(arg1, arg2);
分配数组内存
MyClass* array = new MyClass[5];
或者memset设置内存初始值
使用std::nothrow,new失败时返回null
malloc
cpp
type p = (type)malloc(分配大小)
分配失败时返回null
malloc与new的异同
相同点
都可以动态申请内存
不同点
new是C++操作符,支持重载,还会调用构造函数;malloc是C/C++的标准函数。
new是类型安全的,malloc不安全。
new返回具体指针,malloc返回void型指针,需要类型转换。
new自动计算分配内存大小,malloc需要手动计算。
new是封装了malloc。
C++中的delete
释放内存
cpp
释放单个对象的内存
MyClass* obj = new MyClass(); // 用 new 分配对象
delete obj; // 释放分配的对象内存
释放数组的内存
MyClass* array = new MyClass[10]; // 用 new[] 分配对象数组
delete[] array; // 释放分配的数组内存
一个new对应一个delete
释放内存后需要将指针指向空
delete null是安全的
free
释放内存
cpp
int *array = (int *)malloc(10 * sizeof(int));
free(array);
一个malloc对应一个free
内存释放后将指针指向null,避免产生野指针
free(NULL)会崩溃
new和delete是如何实现的
new的实现过程:对简单的函数直接使用operator new函数;对复杂的数据结构调用operator new函数,分配足够大的原始为类型化的内存,运行该类型的构造函数并传入初始值,最后返回该对象的指针。
delete的实现过程:简单数据类型直接调用free;对复杂的数据结构对指针指向的对象运行析构函数,再用operator delete函数释放对象所使用内存。
new[]一个数组对象,需要知道数组的长度,会多分配4个字节,实际的数组所占内存为p-4;delete[]操作会取出这个数,知道要调用多少次析构函数。
malloc和free的实现
这两个函数是由brk、mmp和munmap这些系统调用实现的。
brk 是将堆顶的指针向高位移动,获得新的内存空间。mmap 是在进程的虚拟地址空间(堆和栈中间,称为文件映射区) 中找到一块空闲的内存块。这两种都是分配虚拟内存,没有分配实际的物理内存。在第一次访问已分配的虚拟地址空间,发生缺页中断(当程序访问的虚拟内存页面不存在物理内存时,会触发缺页中断),操作系统分配物理内存,建立虚拟内存与物理内存的映射关系。
malloc分配内存时,当分配内存小于128k,则使用brk在堆顶向高地址移动指针;当分配的内存大于128k时,使用mmap在虚拟地址空间寻找一块空闲内存。brk分配内存需要等高地址的内存释放后才能释放,而mmap分配的内存可以单独释放。当最高地址空间的空闲内存超过128K,则会执行内存紧缩。
被free回收的内存是立即还给操作系统了吗
不是的,被free回收的内存会用双链表ptmalloc保存,当下次申请内存的时候就尝试再内存中寻找合适的返回,避免反复的系统调用,同时tpmalloc也会尝试合并小块内存,防止产生过多内存碎片。
calloc与realloc
calloc省去了人为空间计算,calloc申请的空间的初始值是0;realloc给动态分配的空间分配额外的空间。
深拷贝和浅拷贝
浅拷贝
cpp
class a{
public:
char *data;
a(const char* str){
data = new char[strlen(str)+1];
strcpy(data, str);
}
a(const a &other) : data(other.data) {} //浅拷贝构造函数
a();
~a();
}
int main()
{
a a1("hello world!");
a a2 = a1;
return 0;
}
a1直接赋值给a2,实际a2使用的内存与a1使用的是同一块,a1内存被回收后,a2会访问无效内存,发生未定义行为。
深拷贝
cpp
class a{
public:
char *data;
a(const char* str){
data = new char[strlen(str)+1];
strcpy(data, str);
}
a(const a &other){
data = new char[strlen(other.data)+1];
strcpy(data, other.data);
}//深拷贝构造函数
a();
~a();
}
int main()
{
a a1("hello world!");
a a2 = a1;
return 0;
}
a2不仅复制了a1的值,还分配了独立内存 。
指针类型
数组指针与指针数组
数组指针:数组是指针;指针数组:成员是指针。
二级指针
一级指针指向某数据类型的内存地址,二级指针指向一级指针的内存地址。
函数指针
cpp
int (*funcPtr)(int, int);
它用于指向函数的内存地址。
指针大小
32位一般是4字节,64位一般是8字节。地址+1是加了一个类型的大小。
*的三种作用
(1)解引用运算符:*指针变量,表示地址操作符,取内容
**(2)指针申明:**表示指针变量
**(3)运算:**表示乘
C++引用变量
声明
cpp
int b = 4;
int &a = b;
是对已存在变量的别名。
常量引用
cpp
const int &value
值不允许修改。
数组引用与指针一样。
指针的引用
cpp
int *a = new int;
int *(&b) = a;
引用与函数
不要引用局部变量。
引用与指针的区别
(1)引用声明的初始化,指针不用马上初始化
(2)引用不能指向空,指针可以
(3)引用初始化后不能指向其他变量,指针可以
(4)引用效率高
(5)引用更安全,指针可以偏移
(6)指针更灵活,直接操作地址;指针更通用,C/C++都可以。
修饰符
static的用法和作用
1.隐藏
2.保持变量内容的持久,即改变变量的生命周期。将static修饰的变量存入静态数据区(全局区)。
3.默认初始化为0。
4.在C++中类成员声明static:
(1)函数体内static变量的作用范围为该函数体,内存只分配一次。
(2)在模块内的static全局变量只能被该模块的函数访问。
(3)模块内的static函数只能被该模块的函数调用。
(4)类中的static成员变量属于类所有,对类的所有对象只有一份拷贝,该变量的初始化要在类外。
(5)类中的static成员函数属于类所有,这个函数没有this指针,只能访问类中的静态成员变量。
类内:
(6)static类对象要在类外初始化,因为static修饰的变量先类存在。
(7)static修饰的类成员属于类不属于对象,所以没有this指针,this指针是指向本成员的指针,所以无法访问非static的类成员。
(8)static成员函数不能被virtual修饰,static成员不属于任何对象和实例,virtual加上没有任何意义;静态成员没有this指针,虚函数的实现是对每个对象分配vptr指针,而vptr指针由this指针调用,所以不能为virtual。虚函数的调用关系:this->vptr->ctable->virtual function。
静态变量什么时候初始化
1.初始化只有一次,在主程序前编译器已经分配好内存。
2.静态局部变量与全局变量一样存在全局区。在C中,初始化发生在执行代码前,编译阶段分配内存后,就会初始化,所以C语言中无法用变量初始化静态局部变量;程序结束,变量所处的全局内存会被回收。
3.在C++中,初始化在执行相关代码时,主要时C++引入对象后,要进行初始化必须要用构造函数或析构函数,构造和析构函数一般需要执行相关的程序,而非简单分配内存。所以C++规定在首次使用到全局或静态变量时进行构造,并通过atexit()管理。在程序结束时,根据构造的顺序反方向析构,所以C++中的静态局部变量可以用变量初始化。
指针与const的用法
int val = 10;
指针常量
int* const a = &val;
常量指针
const int *b = &val;
指针常量是指指针是常量,即指针指向的地址不可变,但指向地址的内容可变。
常量指针是指指针指向的内容不可变,地址可变。
剩下的明天写。