c++-数据类型
整型
- short : 2bytes
- int : 4bytes
- long
- long : long
- char : 1bytes
int num; // 变量声明,开辟一个内存空间
num=1; // 变量赋值
取值范围与内存溢出
可以理解为:取值范围是一个闭环(如果超过范围就按照闭环去取值 )
limits
#include <climits>
宏定义
与数据类型有关的取值范围
浮点型
float 是有效位只有6位或7位(以科学计数法表示的情况下)
声明的时候,尽量使用double类型,编译器默认也是double类型
注意运算时的精读丢失
浮点型cout
默认显示后六位,四舍五入
setf显示精读
double 和 float 显示的精读不一样
cout.setf(ios_base::fixed,ios_base::floatfield);
float a=10.0/3.0; // cout默认显示小数点后六位
const float b=1.0E6;
cout<<a*b<<endl; // 显示精读不一样
double c=10.0/3.0;
cout<<a*b<<endl; // 显示精读不一样
浮点数的存储机制(还要再看 )
二进制的科学计数法
整数部分和小数部分
小数部分转换成二进制要乘2,然后取整数部分
小数转成二进制:1.xxxx2E3(整数部分一定是1)
见视频
字符型
char ch='M'; // 77
int i=ch; // 77
转义字符
转义字符实际占用一个字节
宽窄字符
宽字符: 存储数据使用固定的字节数量
窄字符:存储数据使用不同的字节数量
字符串型 - string
c语言字符串
char *str1="hello world"; // valid in c, invalid in c++
const char *str2="hello world"; // valid in c, valid in c++
char *str3=(char *)"hello world"; // valid in c, invalid in c++
进阶写法
void func(const char *str)
{
int len=strlen(str);
char *new_str=new char[len+1];
strcpy(new_str,str);
}
string
字符串的定义
#include <string>
using namespace std;
int main()
{
string s1; // 默认值是空字符""
string s2="hello"; // 将const char *类型转换成string类型才赋值的
// 可以进行赋值
string s3=s2; // 通过变量赋值
// 通过构造的方式定义
string s4(5,'s'); // 5个s -> "sssss"
}
定义进阶
string 字符串属于调用构造函数,赋值属于拷贝构造
char* 字符串属于定义指针,赋值属于指针传递
#include <iostream>
#include <string>
using namespace std;
void func(string str)
{
cout<<str<<endl;
}
int main()
{
string str1="zhangsan";
string str2(str1); // 拷贝构造
string str3=str1;
string str4=str1+" "+str2;
func(str1);
func(str2);
func(str3);
func(str4);
return 0;
}
string与C字符串
string
字符串对象结尾没有\0
c字符串结尾有\0
string
字符串可以进行赋值 (相当于深拷贝就可以了,相当于是可变对象)
c字符串 指针定义(char*
)可以进行赋值(复制复制),字符数组(char []
)定义不可以赋值(复制复制)
string
–s.length()
计算长度
c字符串 –strlen()
计算长度
互相转化
不能用c语言数组型进行转换
// c转strig
#include <string> // length
#include <cstring> // strlen
char *a="hello";
string b;
b=a;
cout<<b<<endl;
// string转c
// 注意转化后是const 内容,不能被修改
string s1="hello";
const char *s2=s1.c_str();
字符串操作
- 索引
使用索引访问
s[idx]
str.at(idx)
返回指定索引的字符
区别:
[]
索引越界不会抛出异常,仍然输出数据,at
索引越界会抛出异常
- 拼接
+
使用c++运算符重载的方式,不用担心内存溢出
string类型、c字符串类型(数组、指针)、字符,都可以进行拼接
string s1="hello";
string s2="world";
char *s3="qt";
char s4[]="mfc";
char c5='@';
string dst1=s1+s2;
string dst2=s1+s3;
string dst3=s1+s4;
string dst4=s1+c5;
cout<<dst1<<endl;
cout<<dst2<<endl;
cout<<dst3<<endl;
cout<<dst4<<endl;
- 插入
str.insert(pos,string)
注意第一个参数pos可能会越界
string s1="hello";
s1.insert(3,"world");
cout<<s1<<endl;
- 删除
str.erase(pos,len)
,不指明len则删除到结尾
pos越界会异常,len越界会到结尾
string s1="hello";
s1.insert(3,"world");
cout<<s1<<endl;
s1.erase(3,5);
cout<<s1<<endl;
- 提取子串
str.substr(pos,len)
pos越界会异常,len越界会到结尾
string s1="hello";
s1.insert(3,"world");
cout<<s1<<endl;
string s5=s1.substr(3,5);
cout<<s5<<endl;
- 查找
str1.find(str2,start_pos)
从str1的(下标)start_pos开始查找
str1.find(str2,end_pos)
最多找到str1的下标(end_pos)
int idx=str1.find(str2,start_pos)
int idx2=str1.rfind(str2,end_pos)
- 其他函数
str.append()
str.find_first_of() // 查找共同字符的索引
bool 类型
c 语言一般没有bool类型
#include <stdbool.h>
bool flag=true;
c++ 直接支持bool类型数据
注意: cout bool类型还是0、1
typeid
获取一个表达式的类型信息
typeid(dataType)
typeid(expression)
返回一个type_info
类型的对象里边,并返回对象的常引用
类型信息:创建数据的模板,数据占用多大内存,能进行什么样的操作,该如何操作等,都由类型信息决定
class People
{
};
struct Student
{
/* data */
};
int main()
{
int n=100;
const type_info &nInfo=typeid(n);
cout<<nInfo.name()<<" "<<nInfo.hash_code()<<endl;
const type_info &dInfo=typeid(20.4);
cout<<dInfo.name()<<" "<<dInfo.hash_code()<<endl;
const type_info &clsInfo=typeid(People);
cout<<clsInfo.name()<<" "<<endl;
const type_info &stuInfo=typeid(Student);
cout<<stuInfo.name()<<" "<<endl;
return 0;
}
类型转换进阶
https://blog.csdn.net/liranke/article/details/5297087 – 没看
https://blog.csdn.net/p942005405/article/details/105783090
https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used – 还没看
https://www.quora.com/How-do-you-explain-the-differences-among-static_cast-reinterpret_cast-const_cast-and-dynamic_cast-to-a-new-C+±programmer – 还没看
https://stackoverflow.com/questions/28002/regular-cast-vs-static-cast-vs-dynamic-cast?noredirect=1&lq=1 – 还没看
数据类型实际上就是用来解释和定义内存中的数据
数据类型转换是对数据所占用的二进制位进行重新解释,如果有必要,在重新解释的同时还会修改数据,改变它的二进制位
对于隐式类型转换,编译器根据已知的转换规则来决定是否修改数据的二进制位(修改副本数据)
对于强制类型转换,由于没有对应的转换规则,所以只能重新解释数据的二进制位,但是无法对二进制位进行修正(修改副本数据)
具体的修改数据方式 – 没看(见文档P-400)
隐式类型转换是安全的,强制类型转换能够在更大范围的数据类型之间进行转换,但是不安全
最好不使用强制类型转换
(类型转换只能发生在相关类型或者相近类型之间,毫不相关的不行)(没有继承关系的类不能进行转换;基类不能向派生类转换;类类型不能向内置类型转换;指针和类类型之间不能转换)
static_cast
const_cast
reinterpret_cast
dynamic_cast
xxx_cast<newType>(data)
newType
是要转换的新类型,data是被转换的数据
// c语言
float a=10.2;
int b=(int)a;
// c++
float a=10.2;
int b=static_cast<int>(a);
static_cast
在编译期间进行转换,转换失败会抛出编译错误
只能用于良性转换,一般不会导致意外发生,风险很低
在进行 static_cast 强制类型转换时,会按照新的类型进行数据的解释和存储,但并不会改变原始的数据,因为 static_cast 只是对原始数据的重新解释。例如,将一个整型变量转换为浮点型变量时,static_cast 会重新解释整型变量的位模式,并将其存储在浮点型变量中,但并不会改变原始的整型变量数据。
需要注意的是,在进行 static_cast 强制类型转换时,需要确保类型之间存在某种已知的转换方式,否则会导致数据的不确定性和错误。
可转换类型
用于基本类型之间的转换
用于将基类指针转换为派生类指针
用于void指针与任何类型指针之间的互换
// 例如
short -> int
int -> double
非const -> const
基类 -> 派生类
void* -> int*
char* -> void*
还可以利用转换构造函数或类型转换函数,但是指针不可以
不可转换类型
不能用于在两个具体类型的指针之间进行转换
不能用于无关类型之间的转换(这些是有风险的)
// 例如
int* -> double* //ERROR
student* -> int* //ERROR
例子1
int a=10;
int *pa=&a;
double b=static_cast<double>(a);
char c=static_cast<char>(a);
cout<<"a "<<a<<endl;
cout<<"b "<<b<<endl;
cout<<"c "<<c<<endl;
// double *pb=static_cast<double*>(pa); // ERROR
// char *pc=static_cast<char *>(pa); // EORROR
void *p=static_cast<void *>(pa);
int *pa2=static_cast<int *>(p);
cout<<*pa2<<endl;
例子2
#include <iostream>
using namespace std;
class Complex
{
private:
double real;
double imag;
public:
Complex(double real=0.,double imag=0.0);
/*
类型转换函数
*/
operator double() const ;
};
Complex::Complex(double real,double imag)
{
this->imag=imag;
this->real=real;
}
Complex::operator double() const
{
return this->real;
}
int main()
{
int m=100;
Complex c(10.,20.);
long n=static_cast<long>(m); // 没有信息丢失
char ch=static_cast<char>(m); // 向下转换可能有信息丢失
int *p1=static_cast<int *>(malloc(10*sizeof(int))); // void* 转换int*
void *p2=static_cast<void *>(p1); // 具体类型转换为void*
/*
调用类型转换函数
*/
double real=static_cast<double>(c);
/*下面的不行*/
// float *p3 = static_cast<float*>(p1); //不能在两个具体类型的指针之间进行转换
// p3 = static_cast<float*>(0X2DF9); //不能将整数转换为指针类型
cout<<n<<endl;
cout<<ch<<endl;
return 0;
}
不可转换类型
- 用于执行与运行时多态性相关的转换,如将基类指针或引用转换为派生类指针或引用,因为在运行时无法确定指针或引用所执行的的实际类型,因此需要使用dynamic_cast
class Base
{
private:
/* data */
public:
Base(/* args */){}
virtual void func()
{
cout<<"Base func"<<endl;
}
};
class Derive:public Base
{
private:
/* data */
public:
Derive(/* args */){}
virtual void func() override
{
cout<<"Derive func"<<endl;
}
};
Base *baseptr=new Base();
Derive *deriveptr=new Derive();
Base *deriveptr2baseptr=static_cast<Base*>(deriveptr);
// Derive *baseptr2deriveptr=static_cast<Derive *>(baseptr);
用于执行不安全的转换,如将指针或引用转换为不相关的类型,在这种情况下,应该使用reinterpret_cast
int i=0;
// double *pd=static_cast<double *>(&i) // ERROR
double *pd2=reinterpret_cast<double *>(&i) // reinterpret_cast可以进行不安全的类型转换
const_cast
const_cast将const转换为非const
将volatile转为非volatile
并不会修改原始的值,这是因为,const_cast只是强制去掉了指针p的const属性
#include <iostream>
using namespace std;
int main()
{
const int n=100;
int *p=const_cast<int *>(&n);
*p=234;
cout<<n<<endl; // 100
cout<<*p<<endl; // 234
return 0;
}
reinterpret_cast
https://zhuanlan.zhihu.com/p/33040213
reinterpret_cast仅仅是对二进制位的重新解释,不会借助已有的转换规则进行调整,风险很高
一般不能用static_cast完成的转换,就可以用reinterpret_cast进行转换
感觉就是无视规则,尽量不要使用
用于两个具体类型指针之间的转换
不能用于普通类型之间的转换,只能用于指针的转换,但是是不安全的
不能用于自定义类型之间的转换,只能用于自定义对象指针之间的转换,注意安全性
例子1
#include <iostream>
using namespace std;
class Base
{
private:
int a;
int b;
public:
Base(int a=0,int b=0);
};
Base::Base(int a,int b)
{
this->a=a;
this->b=b;
}
int main()
{
char str[]="hello world";
float *p1=reinterpret_cast<float*>(str); // char* 转换为float*
cout<<*p1<<endl; // 完全是错误的输出
return 0;
}
例子2
class Base
{
private:
/* data */
public:
Base(/* args */){}
virtual void func(){
cout<<"Base func"<<endl;
}
};
class Derive:public Base
{
private:
/* data */
public:
Derive(/* args */){}
virtual void func() override
{
cout<<"Derive func"<<endl;
}
};
int main()
{
int a=10;
int *pa=&a;
double b=static_cast<double>(a);
char c=static_cast<char>(a);
// float d=reinterpret_cast<float>(a); // EORROR
double* d=reinterpret_cast<double*>(pa); // 不会出错,但是数不对
printf("%lf\n",*d); // 错误
printf("%lf\n",(double)a);
// int e=reinterpret_cast<int>(b); // ERROR
int *f=reinterpret_cast<int *>(&b); // 不会出错,但是数不对
printf("%d\n",*f); // 错误
Base base;
Derive derive;
Base *baseptr=new Base();
Derive *deriveptr=new Derive();
// Base derive2Base=reinterpret_cast<Base>(derive); // ERROR 不能用于对象
// Derive base2Derive=reinterpret_cast<Derive>(base); // ERROR 不能用于对象
Base *deriveptr2baseptr=reinterpret_cast<Base*>(deriveptr); // 正确
// Derive *baseptr2deriveptr=reinterpret_cast<Derive *>(baseptr); // 虽然有些情况下是可以的,但是不推荐
return 0;
}
dynamic_cast
用于基类和派生类之前的转换
dynamic_cast 是 C++ 中的一种类型转换运算符,用于在运行时将一个指向基类对象的指针或引用强制转换成派生类对象的指针或引用。它通常用于处理多态性问题,即在运行时确定对象的实际类型。
如果 dynamic_cast 转换失败,则返回一个空指针(nullptr)或抛出 std::bad_cast 异常。使用 dynamic_cast 进行转换时,必须保证基类指针或引用指向的对象是一个有效的派生类对象,否则转换结果是未定义的。此外,dynamic_cast 只能用于类层次结构中的有继承关系的类型之间的转换,不能用于无继承关系的类型之间的转换。
结构体
结构体与构造函数
https://blog.csdn.net/qq_46527915/article/details/114580713
当结构体内变量很多时,(先定义结构体变量,然后再对其中的元素逐一赋值,并不是很方便),因此使用构造函数的方法对结构体进行初始化
构造函数,是直接定义在结构体中
构造函数不需要写返回值类型,且函数名与结构体名相同
由于构造函数的存在,才可以直接定义结构体变量,而不进行初始化
对于默认构造函数
对于整数类型成员,其默认值为0
对于布尔型类型成员,其默认值为false
对于指针型类型成员,其默认值为nullptr
下面的例子中,
stu(){}
就是默认生成的构造函数,如果不自己定义该默认构造函数,编译器自动生成一个
struct stu
{
int a;
char *name;
// 构造函数
stu(){} // 注意不写分号
};
下面的例子是自定义构造函数
struct stu
{
int a;
char *name;
// 构造函数
stu(int _a,char * _name){
a=_a;
name=_name;
} // 注意不写分号
};
下面的例子是,以初始化列表的方式自定义构造函数
struct stu
{
int a;
char *name;
// 构造函数
stu(int _a,char * _name):a(_a),name(_name){} // 注意不写分号
};
注意:
只定义默认构造函数(无参数构造函数),可以定义结构体变量,而不进行初始化
只定义自定义构造函数(有参构造函数),必须在定义结构体变量的时候,进行初始化
完善的写法
struct stu
{
int a;
char *name;
// 默认构造函数
stu(){} // 注意不写分号
// 构造函数
stu(int _a,char * _name):a(_a),name(_name){} // 注意不写分号
// 构造函数
stu(char * _name){
name=_name;
} // 注意不写分号
};
初始化
// 使用默认构造函数进行初始化
// 并通过ptr指针返回指向该结构体的地址
struct stu *ptr=new stu;
struct stu *ptr_=new stu(); // 这两种方法等价
// 调用自定义构造函数
struct stu *ptr1=new stu{10,"li"};
struct stu *ptr1_=new stu(10,"li");
struct stu *ptr2=new stu{"wang"};
struct stu *ptr2_=new stu("wang");
delete ptr;
delete ptr1;
delete ptr2;
结构体高级用法
位域成员
#include <iostream>
using namespace std;
int main() {
/*
使用冒号: 来显示数据所占的位数
最好是使用相同的数据类型
最终的结构体的占用内存空间 实际上根据编译器定的
*/
typedef struct {
unsigned int bit1 :1;
unsigned int bit2 :2;
unsigned int bit3 :3;
}mStruct;
cout<<sizeof(mStruct)<<endl; // 4
return 0;
}
很重要
就按尝试来就行了
#include <iostream>
using namespace std;
typedef union
{
unsigned char data[2];
/*
7 6 5 4 3 2 1 0
d7 d6 d5 d4 d3 d2 d1 d0
e.g.
0x55:
0 1 0 1 0 1 0 1
*/
struct
{
unsigned char d;
unsigned char d0 :1;
unsigned char d1 :1;
unsigned char d2 :1;
unsigned char d3 :1;
unsigned char d4 :1;
unsigned char d5 :1;
unsigned char d6 :1;
unsigned char d7 :1;
}Bits;
}U;
int main(int argc, char const *argv[])
{
U mu;
mu.Bits.d=0xAA;
mu.Bits.d0=1;
mu.Bits.d1=0;
mu.Bits.d2=1;
mu.Bits.d3=0;
mu.Bits.d4=1;
mu.Bits.d5=0;
mu.Bits.d6=1;
mu.Bits.d7=0;
printf("%#x\n",mu.data[0]); // 0xaa
printf("%#x\n",mu.data[1]); // 0x55
for (int i=0;i<8;i++)
{
printf("%d ",((mu.data[1]>>i)&0x01)); // 1 0 1 0 1 0 1 0
}
return 0;
}