C++动态内存分配、名称空间、全局变量、常量、字符串基础、类型转换、回调函数
#include <iostream>
#include <string> //<string>头文件不是<string.h>头文件,在c++中<string.h>被调整为<cstring>,<string>是提供string类的相关函数的头文件
int mainnew1()
{
int* pi = new int; //new关键字,相当于(int*)malloc(sizeof(int)) ,返回动态内存地址,需要用delete关键字释放
*pi = 20; //赋值
delete pi; //相当于free(pi)
int* pi2 = new int(20); //初始化, new 类型(值)
delete pi2;
struct xy { int x; int y; };
xy* pxy = new xy; //new是动态内存分配,返回的是地址,所以需要指针接收
pxy->x = 20;
delete pxy;
xy* pxy2 = new xy{ 1,2 }; //初始化结构使用{}
delete pxy2;
int* pia = new int[5]; //创建数组,同样使用int*指针接收,new int用int*接收,new int[5]也用int*接收,相当于(int*)malloc(5*sizeof(int))
pia[1] = 20;
delete[]pia; //数组释放写作 delete[]变量名 ,无论几维数组都只写一个[]
//new关键字和new[]关键字是两个关键字,delete和delete[]同理
int* pia2 = new int[5] {0}; //数组初始化使用{}
memset(pia2, 0, sizeof(int[5])); //常用memset(内存地址,初始化值,size)进行初始化
delete[]pia2;
char* pc = new char; //创建char大小内存,使用char*接收地址
delete pc;
char** ppc = new char*(NULL); //创建char*大小内存,使用char**接收地址
delete ppc;
char** parr = new char* [5]{NULL}; //创建char*[5]指针数组大小的内存,使用char**接收地址,数组初始化NULL对应指针元素
delete[]parr;
char(*pca)[4] = new char[3][4]{{'a'}}; //创建二维数组,指针指向数组的每个元素(即一维数组),初始化{{},{},{}}二维所以两层大括号
delete[]pca;
char* ptr = NULL;
delete ptr; //delete可以用于指向null的指针
ptr = NULL;
delete[] ptr; //delete[]也可以
return 0;
}
#include <new> //new头文件包含定位new运算符
char staticmem[256];
void mainnew2()
{
int* ptr = new(staticmem)int[10]; //定位new运算符,使用new后面括号内指定的地址(void*)存放int[10]内存块并返回内存块的地址,所以ptr的值和staticmem的值相同,由于没有对内存块进行初始化,所以该区域依然保持全局变量默认的零初始化值
ptr[0] = 97; //修改第一个int元素,值0-255对应低阶8位第一个字节,所以后面三个字节没有改变
std::cout << staticmem[0] ; //结果为a,char数组第一个元素即内存块中第一个字节修改为97,说明这里组成int的四个字节按地址最前面的字节最低阶,最后面的字节最高阶
//定位new运算符使用已有内存块来分配内存,所以不需要单独释放delete
}
inline void clean_input() //内联函数,向编译器申请将该函数转换为内联代码,编译器根据情况决定,c++推荐使用内联函数取代类函数宏的内联代码
{
while (getchar()!='\n')
{
continue;
}
}
int globali; //全局变量
short globals; //没有显式初始化的静态变量会初始化为0
double globald;
char globalc;
int globali2 = 10; //显示初始化,静态变量在显示初始化之前也会被零初始化
const int constantinternal = 10; //在c++中const修饰的全局变量默认带有static,内部链接,这使得在头文件中声明的常量会在每个包含的源文件中产生一个备份,这和c中不同(c中不默认带),c++中定义常量必须进行初始化,不允许默认零初始化
extern const int constexternal = 20; //如果要使用外部链接的常量,需要使用extern const,这种写法后面不带初始化则为引用式声明,后面带初始化则为定义式声明,同样只能存在一个定义
int mainnamespace1()
{
std::cout << ::globali<<std::endl; //::作用域解析运算符,用法 名称空间::标识符 ,cout为std名称空间中定义,globali在当前文件中定义为全局变量并且没有命名名称空间,所以::前为空
std::cout << "enter:";
::clean_input(); //普通函数调用省略::
using std::cout; //using声明,将特定的名称添加到它所属的声明区域中,载入std名称空间中的cout,当前作用域内从当前语句以下部分有效,using声明添加的标识符不能在当前声明区域中再被用作标识其他变量,同样,在同一个声明区域中已经被使用的同名标识符不能使用using声明添加,using声明在使用方面就像是声明了一个变量(指不允许当前作用域重名)
//报错:int cout = 29; 与using声明冲突
cout << globali; //全局变量调用省略::
return 0;
}
namespace namespace1 { //定义名称空间
int x = 10; //全局变量
struct ss { int i; }; //结构
void func() { /*函数略*/}
namespace inner { int innerx; } //名称空间
}
namespace namespace1 { //同名的名称空间会被合并,如在头文件和类库中使用相同的名称空间包裹,才能通过该名称空间调用
int y = 2;
}
namespace namespace2 { //不同的名称空间
int x = 3;//两个名称空间中的x不冲突
int y = 9;
}
int mainnamespace2()
{
using namespace std; //using编译指令,载入std名称空间所有内容,如果当前作用域内有重名标识符可能产生混乱
cout << namespace1::x << " " << namespace2::x << endl; //两个名称空间的x不冲突
{
using namespace namespace1; //通过创建块作用域来暂时载入
x = 300;
}
//离开块以后不再能直接调用x,但之前的更改依旧有效,因为是全局变量
{
using namespace namespace2;
x = 400;
}
cout << namespace1::x << " " << namespace2::x << endl; //两个x都改变了
//{
// using namespace namespace1; //如果同时载入两个名称空间
// using namespace namespace2;
// x = 20; //在使用两个名称空间中同名变量时报错
//}
return 0;
}
int var1; //全局名称空间的全局变量var1
namespace ns3 { int var1; } //名称空间ns3中的变量var1
void mainnamespace3()
{
using std::cout; //using声明
using std::endl;
int var1; //局部变量var1;
cout << &var1 << endl; //使用局部变量
cout << &::var1 << endl; //使用全局变量
cout << &ns3::var1 << endl; //使用名称空间ns3中的变量
using namespace ns3; //using编译指令不同于using声明,与局部变量名冲突不会直接报错但会增加用错的几率
cout << &var1 << endl; //依然使用局部变量,局部变量会盖住名称空间中的同名变量,其他名称不冲突的变量可以正常访问
cout << &::var1 << endl; //使用全局变量
cout << &ns3::var1 << endl; //虽然被局部变量盖住,但依然可以通过作用域解析运算符来使用名称空间ns3中的变量
}
//如果在全局声明区域中使用using namespace ns3; 会使其他函数调用全局var1时产生二义性报错
double var3=10;
/*------test_namespace.h-----*/
namespace ns5 {
//namespace在头文件中使用时也要遵循头文件的用法,不应在头文件中定义外部链接的静态变量(这会导致包含该头文件的多个源文件重复定义冲突)
struct ns5struct { int a; }; //头文件中的名称空间中声明结构
void ns5func(); //头文件中不包含普通函数的定义
extern double var3; //引用在源文件中定义的ns5::var3变量,在名称空间中直接使用的标识符默认为该名称空间::标识符,这个引用不会引用到::var3当前文件的全局变量,所以如果要引用当前文件的全局变量应写作extern double ::var3
}
/*------test_namespace.cpp-----*/
namespace ns5 {
//在cpp文件中追加同一名称空间的内容:函数定义、定义式声明等
double var3 = 20; //在头文件中引用的var3会使用这个定义式声明,任何包含头文件的翻译单元都能使用ns5::var3,但如果头文件中没有引用式声明ns5::var3,就和普通外部链接变量一样没有引用式声明就无法使用该变量
void ns5func() {}
}
void test_string2()
{
using namespace std;
string s1 = "c++中使用string类表示字符串"; //使用string不再需要担心字符数组索引越界
string s2("另一种初始化方式");
string s3(s1); //使用变量初始化
string* pstr = new string; //new返回地址,所以需要string*接收
delete pstr;
string* pstr2 = new string("asdf"); //动态分配一块内存以存储string,并初始化为"asdf",后将地址返回,通过指针接收
delete pstr2;
s1 += s2; //将s2字符串拼接到s1后面
cout << s1.size() << endl; //返回s1的长度,不包括\0
if (s1 == s2) //相当于strcmp==0
cout << "s1和s2内容相同" << endl;
else if (s1 < s2) //相当于strcmp<0
{
cout << "根据字符集顺序s1<s2" << endl;
}
s1[1] = '-'; //和字符数组同样可对每个字符进行更改
const char* pc = s3.c_str(); //.c_str()返回字符串的地址
s1.swap(s3); //将s1和s3交换
cout << s1.find('+') << endl; //.find(char,size_t)在s1中查找参数1,找到返回索引,没找到返回-1,有参数2默认为0即从头开始找,结果1
cout << s1.find('+', 2) << endl; //从索引2开始找,结果2
cout << s1.find("++") << endl; //.find(char*,size_t)重载,查找字符串
string strarr[5] = { //string数组,每个元素都是string
"1",
"12",
"123",
"1234",
"12345"
};
s1.erase(0, s1.find_first_not_of('c')-0); //.erase(size_t,size_t)函数对字符串进行删节,从参数1指定的下标/索引位开始删,参数2指定删多少个,.find_first_not_of(char)查找第一个不是参数1字符的字符,返回下标,c下标为0,返回'+'的下标1,.erase(0,1)从0下标开始删,删1个,新的字符串比之前的少第一个字符
s1.erase(s1.find_last_not_of(' ') + 1); //参数2默认size_t最大值,即会一直删到\0为止,查找最后一个不是空格的字符返回下标,为修剪掉字符串末尾的空格,需要从该下标的下一个下标开始删所以+1
s1.erase(); //参数1默认0,参数2默认最大,执行后s1字符串为空字符串
string sss1("ying"); //用字符串为string传参
sss1 = sss1 + "yyy"; //拼接字符串
sss1 = "xxx" + sss1; //也可以将字符串拼接到string对象前面
cin >> s1; //>>抽取运算符持续读取一个单词即停,这种用法string对象不能存储一整行带空格内容
getline(cin, s1); //<string>头文件中的函数,使用istream对象读取一整行存储到string对象中
sss1.compare(""); //和参数字符串比较,相同返回0不同返回1
}
void check(std::string& ss)
{
const char* ptr;
while (std::cout << "q to quit:", getline(std::cin, ss)) //全局函数getline(cin,string)返回cin,在while条件判断中用于判断读取是否成功
{
if (1 == ss.size() && 'q' == ss[0]) //也可以判断 !ss.compare("q")
break;
ptr = ss.c_str();
while (*ptr)
{
std::cout.put(toupper(*ptr++));
}
if (ss.size()) //读取到空行时.size()==0
{
std::cout << std::endl;
}
}
}
int maincast1()
{
float f;
int i = 10;
f = i; //隐式转换
f = (float)i; //c强转
f = static_cast<float>(i); //c++显式转换,编译时会检查目标类型和源类型是否相关
void* pv;
int* pi = NULL;
double* pd;
pv = pi; //隐式
pv = static_cast<void*>(pi); //显示转换
pd = reinterpret_cast<double*>(pi); //c++强转对内存重新解释,使用reinterpert_cast,不能删除const特性,不能将源类型转换为size更小的类型,不能将函数指针和数据指针相互转换。
const char* pc = "sss";
char* pnc = const_cast<char*>(pc); //将const的指针/引用解除const限定,比如有一个func(char*)读取非const参数,但是函数内部并不改变该指针,要想使用这个函数就需要const_cast脱掉const,<>中的类型需要和原类型除const部分相同,这里是const char*转char*,对于const值通过const_cast删除const特性后进行修改的结果是不确定的
int ii = 20;
const int* pii = ⅈ
int* piii = const_cast<int*>(pii); //但对于const指针指向的非const值,使用const_cast删除指针的const特性进行修改值是允许的
*piii = 30; //因为ii是非const所以可以修改
//错误:double* pdd = const_cast<double*>(pii); const_cast只能修改const/volatile特性,其他部分需要和原类型相同
//dynamic_cast动态转换后续介绍
return 0;
}
double add01(double d1, double d2) { return d1 + d2; }
double calculate01(double d1, double d2, double (*callback_func)(double, double)) //接收一个函数指针参数
{
return callback_func(d1, d2); //在函数内调用接收的函数指针来处理变量
}
void maincallback1()
{
double d1 = 10.0;
double d2 = 10.2;
calculate01(d1, d2, add01); //main为主调函数,calculate01为被调函数,add01为回调函数,calculate01向主调函数索要一个回调函数用于函数内部处理数据,用户通过定义回调函数决定calculate01的行为
}