变量和基本类型
算术类型
| 数据类型 | 最小尺寸 | 补充 |
|---|---|---|
| bool | 1 byte | 布尔类型 |
| char | 1 byte | 字符 |
| wchar_t | 2 byte | 宽字符 |
| wchar16_t | 2 byte | Unicode字符 |
| wchar32_t | 4 byte | Unicode字符 |
| short | 2 byte | 短整型 |
| int | 4 byte | 整型 |
| long long | 8 byte | 长整型 |
| float | 4 byte,6 位有效数字 | 浮点型 |
| double | 4 byte,10 位有效数字 | 浮点型 |
long类型所占字节数和平台有关,如VS2019中为4bytes,而linux64中为8bytes, long double也有类似问题. float小数可能发生进位,但不是简单按照四舍五入的.
这里引出结构体字节对齐问题,如何结算结构体所占字节数.
结构体内存对齐
为何要内存对齐
假设内存不对齐,a为char类型占一个字节,b为int类型占4个字节,内存排布如下:
从内存读取数据一般是一个个内存块内读取的,每次读取一般都是4bytes的整数倍. 若内部不对齐,假设每次读取4bytes,为了读取a,b,需要读取a的1个字节,b的前3个字节,那么读取b就要分两次,最后还要拼接组合读取效率低
结构体如何对齐
- 成员1在结构体偏移位0的地址处(首地址)
- 其他成员var对齐到N的整数倍地址上(,k为指定对齐的值,64位机器上一般为8)
- 结构体大小 % 最大对齐数(所有成员(2)中的) == 0
- 嵌套结构体对齐到自己的最大对齐整数倍出,结构体总大小 % 最大对齐数 == 0
最后一条,计算出变量在内存中最大地址偏移量对应的字节数,然后调整为最大对齐数的整数倍.
struct B {
char x; //y---|xxxx| -->8
int y = 1;
};
struct A {
char a; //a---|y---|xxxx| -->12
struct B tmp;
};
struct C {
char a; //ay--|xxxx| -->8
char x;
int y = 1;
};
int main()
{
cout << sizeof(B) << endl; //8
cout << sizeof(A) << endl; //12
cout << sizeof(C) << endl; //8
return 0;
}
类型转换
类型1 var = 数据(类型2)
int main()
{
unsigned char a = -1;
unsigned char b = 255;
unsigned char c = 256 + 97;
unsigned char d = 97;
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
cout<<d<<endl;
return 0;
}
unsigned char表示无符号数,可以表示0-255区间的值, -1不属于该区间,
实际使用要避免混用带符号数据和无符号数据
一个易错点: 在for循环中,下面这种写法不会得到想要的效果,因为你当j等于0时, j - 1得到4294967295,循环将无法终止.
for(unsigned j = 5;j>=0;--j){
cout<<j<<endl;
}
建议:第一次使用变量时再定义
- 方便找,赋值方便
- 程序可能提前退出,后面定义就不需要,节约资源
复合类型: 指针和引用
C++11增加了右值引用,一般提引用说的是左值引用.
引用: 别名,给已经存在的对象起的另一个名字 特性:
- 必须被初始化,之后一直和初始化对象绑定,无法重新绑定到另一个对象.
- 可以通过引用更改对象
指针 : 一个变量,存放另一个变量的地址
int a;
int &b = a;
int *p;
与引用区别:
- 无需赋初值,当然这样会拥有一个不确定的值
- 可以更改指向对象
- sizeof得到结果不一样,64位机器上,指针类型得到的是8个字节,而引用取决于绑定对象大小
- ++含义不一样,
p++表示指向后面一部分内存,b++表示a++.
关键字,限定符
extern 关键字
分开变量定义和声明,若extern语句指定了初值,就变成了定义
extern int a; //声明
int b ; //定义
extern int c = 2; //定义
用途: 分离式编译使用同一变量 如test.cpp中定义全局const变量num, int num = 5, main.cpp中需要用到这个变量,方法有:
- include包含这个文件
- 在main.cpp中声明: extern int num,然后使用它. (命令行编译 gcc -o main main.cpp test.cpp -Wall )
当然这样可能导致在main.cpp中更改了变量num,所以在test.cpp,和main.cpp中都加上const修饰,看起来一切完美,再编译发生如下错误:
undefined reference to num . 原来在C++11中,当变量使用const修饰而又没有使用extern修饰,且此前没有被声明为external linkage时,它是internal linkage,若想使用,则需要在test.cpp中修改为:
extern const int num = 5;
const关键字
作用: 限定对象只读.
const对象创建后不能更改,所以const对象必须被初始化. 默认情况下,const对象只在文件内有效(和前面所说一样,想用就在定义时加extern)
cosnt引用
对const对象的引用,修饰后不能通过引用更改对象的值.但绑定对象是可以通过其他方式改变的.
int a = 42;
int &b = a;
const int &c = a;
cout<<c<<endl; // c = 42
b = 4;
cout<<c<<endl; // c = 4
对象是常量,引用或者指针也必须用const修饰.
const指针
const *ptr: 指向常量的指针,不能通过指针修改值
*const ptr: 常量指针,指针指向不能修改
int a = 3;
int *const ptr1 = &a; // 指针是常量
int const *ptr2 = &a; //
const int * const ptr3 = &a;
int b = 5;
//ptr1 = &b; // error
*ptr1 = 3;
cout<<a<<endl; // 3
ptr2 = &b;
//*ptr2 = 5; //error
//ptr3 = &b; // error
// *ptr3 = 6; // error
顶层const和底层const: 顶层const表示指针本身是常量,底层const表示指针所指对象是常量.
constexpr
C++11赋予const的是只读属性,而constexpr表示后面是常量或者常量表达式,编译期可以计算出来.
const int a = 5;
int *p = (int*)&a;
*p = 8;
cout<<a<<endl;
cout<<*p<<endl;
cout<<&a<<endl;
cout<<p<<endl;
以上程序中,C++执行结果显示a = 5,*p = 8,二者地址一样.而c语言中,二者值都变成8. 地址一样值不一样怎么解释呢? 下面查看C++,c程序对应的汇编代码
C++
cout << a << endl;
00BA1BB5 mov esi,esp
00BA1BB7 push 5
...
C
printf("&a=%x\n", &a);
00D01885 lea eax,[a]
00D01888 push eax
00D01889 push offset string "&a=%x\n" (0D07B30h)
00D0188E call _printf (0D010CDh)
00D01893 add esp,8
由此可知:差异是编译器优化造成的,通过p的确改变了a的值,但是C++里面a值读了一次,后面再用到都是用它对应的常量替换,而没有从地址处重新读取. 而c语言里面则重新读取了a的地址.
const和constexpr的具体使用的不同:
constexpr int sqr1(int arg){
return arg*arg;
}
const int sqr2(int arg){
return arg*arg;
}
int main()
{
array<int,sqr1(10)> mylist1; //可以,因为sqr1时constexpr函数
array<int,sqr2(10)> mylist1; //不可以,因为sqr2不是constexpr函数
return 0;
}
auto和decltype
auto
auto推导一般忽略顶层const,保留底层const,看下例:
const int a = 4;
auto c = a; // 取变量名在顶层const
c = 5;
cout<<c<<endl; // 5
auto b = &a; //取地址保留了底层const
b = 3; // error
decltype 推导表达式类型定义变量,但不想用它的结果初始化,就用decltype
int a = 3;
int b = 1;
decltype(a+b) c = 3.14;
cout<<c<<endl; // 3
decltype和引用
与auto不同的是,decltype会返回变量的完整类型(包括引用,顶层const)
decltype(expr)中expr如果是解引用指针,如*p,那么得到的就是引用类型.
若为双层括号,decltype((expr))得到的一定是引用.
字符串
c风格的字符串
c风格的字符串以'\0'结束,看程序:
char arr1[] = { 'a','b','c','d' };
cout << strlen(arr1) << endl; //不确定值
char arr2[] = { 'a','b','c','d','\0'};
cout << strlen(arr2) << endl; // 4
第一个字符串数组不是\0结束,所以用strlen求长度会继续往后找,直到遇到\0,所以结果有误, 第二种求长度会忽略\0
与c代码接口
C++里面使用string,而因历史原因可能使用了c风格字符串,直接传递string类型肯定不行,于是有c_str()接口,但注意有陷阱:
string s = "abc";
const char *str = s.c_str();
s = "bbb";
cout<<str<<endl; // bbb
c_str()返回的是const char*类型,但后面原始的string对象改变了,字符数组s会变.
实现一个简单的string
MyString.h
#pragma once
#include <cstring>
#include <iostream>
using std::ostream;
class String {
friend ostream& operator<<(ostream& os, const String& rhs);
public:
String(const char* rhs = nullptr);
String(const String& rhs);
String& operator=(const String& rhs);
String(String&& rhs) noexcept;
~String();
public:
size_t size() const;
private:
char* str;
size_t len;
private:
void init(const char* s);
};
MyString.cpp
#include "MyString.h"
String::String(const char* rhs /*= nullptr*/)
{
init(rhs);
}
size_t String::size() const
{
return len;
}
void String::init(const char* s)
{
if (s == nullptr) {
str = nullptr;
len = 0;
}
else {
len = strlen(s);
str = new char[len + 1];//确保最后一个字符是\0
//str[len] = '\0';
strcpy(str, s);
}
}
ostream& operator<<(ostream& os, const String& rhs)
{
if (rhs.str == nullptr) {
os << "";
}
else {
os << rhs.str;
}
return os;
}
String::String(const String& rhs) //String S(rhs)
{
//String(rhs.str); //相当于调用了构造函数,即生成了一个新对象
init(rhs.str);
}
String& String::operator=(const String& rhs) // str = str 这种也要考虑到
{
if (this != &rhs) {
delete[] str;
init(rhs.str);
}
return *this;
}
String::String(String&& rhs) noexcept // s(s) 也要考虑到
{
if (this != &rhs) {
delete str;
str = rhs.str;
len = rhs.len;
rhs.str = nullptr;
rhs.len = 0;
}
}
String::~String()
{
if (str != nullptr || len != 0) {
std::cout << "destructor finished!\n";
delete[] str;
len = 0;
}
//std::cout << "destructor finished!\n";
}
main.cpp测试
#include<iostream>
#include "MyString.h"
using namespace std;
int main()
{
{
String str1 = "Hello";
cout << str1 << endl;
cout << str1.size() << endl;
String str2(str1);
cout << str2 << endl;
cout << str2.size() << endl;
str2 = "Good";
String str3 = str2;
cout << str3 << endl;
cout << str3.size() << endl;
str3 = "ABC";
String str4(std::move(str3));
cout << str3 << endl;
cout << str3.size() << endl;
cout << str4 << endl;
cout << str4.size() << endl;
String str5("World!");
cout << str5 << endl;
cout << str5.size() << endl;
str5 = "world";
str2 = str5;
cout << str5 << endl;
cout << str5.size() << endl;
}
system("pause");
return 0;
}