在整个程序生命周期内都是有效的,在定义位置之后的任意函数中都能访问。
全局变量在主程序退出时由系统收回内存空间。
在函数或语句块内部的语句使用,在函数或语句块外部是不可用的。
局部变量在函数返回或语句块结束时由系统收回内存空间。
用static修饰的局部变量生命周期和程序相同,并且只会被初始化一次。
其作用域为局部,当定义它的函数或语句块结束时,其作用域随之结束。
当程序想要使用全局变量的时候应该先考虑使用static(考虑到数据安全性)。
头文件(*.h):需要包含的头文件,声明全局变量,函数的声明,数据结构和类的声明等。
源文件(*.cpp):函数的定义、类的定义。
C++中的字符串操作可以分为两种主要风格:
C风格字符串和C++风格字符串。以下是它们的特点和常用操作,并附带相应的示例代码:
C风格字符串:
\0
表示字符串的结束。C++风格字符串:
std::string
类,它封装了字符串操作。std::string
自动管理内存,无需手动分配或释放内存。C++风格字符串的常用操作:
std::string str = "字符串内容";
str = str + "更多字符串内容";
str += "追加的字符串";
int length = str.length();
if (str1 == str2) { /* 相等 */ } else { /* 不相等 */ }
std::string subStr = str.substr(startIndex, length);
size_t found = str.find("目标子串");
#include
#include
int main() {
// 赋值操作
std::string str1 = "Hello, ";
std::string str2 = "world!";
// 拼接操作
std::string result = str1 + str2;
std::cout << "拼接后的字符串: " << result << std::endl;
// 追加操作
result += " Welcome to C++!";
std::cout << "追加后的字符串: " << result << std::endl;
// 字符串长度
std::cout << "字符串长度: " << result.length() << std::endl;
// 字符串比较
std::string compareStr1 = "Hello, world!";
std::string compareStr2 = "Welcome to C++!";
if (compareStr1 == compareStr2) {
std::cout << "字符串相等" << std::endl;
} else {
std::cout << "字符串不相等" << std::endl;
}
// 截取子串
std::string subStr = result.substr(7, 5); // 从位置7开始截取5个字符
std::cout << "截取的子串: " << subStr << std::endl;
// 查找子串
size_t found = result.find("C++"); // 查找C++在字符串中的位置
if (found != std::string::npos) {
std::cout << "子串 \"C++\" 找到,位置:" << found << std::endl;
} else {
std::cout << "子串 \"C++\" 未找到" << std::endl;
}
return 0;
}
C语言约定:如果字符型(char)数组的末尾包含了空字符\0(也就是0),那么该数组中的内容就是一个字符串。
memset(name,0,sizeof(name)); // 把全部的元素置为0。
name[0]=0; // 不规范,有隐患,不推荐。
char *strcpy(char* dest, const char* src);
功 能: 将参数src字符串拷贝至参数dest所指的地址。
返回值: 返回参数dest的字符串起始地址。
复制完字符串后,会在dest后追加0。
如果参数dest所指的内存空间不够大,会导致数组的越界。
char * strncpy(char* dest,const char* src, const size_t n);
功能:把src前n个字符的内容复制到dest中。
返回值:dest字符串起始地址。
如果src字符串长度小于n,则拷贝完字符串后,在dest后追加0,直到n个。
如果src的长度大于等于n,就截取src的前n个字符,不会在dest后追加0。
如果参数dest所指的内存空间不够大,会导致数组的越界。
size_t strlen( const char* str);
功能:计算字符串的有效长度,不包含0。
返回值:返回字符串的字符数。
strlen()函数计算的是字符串的实际长度,遇到0结束。
字符串拼接strcat()
char *strcat(char* dest,const char* src);
功能:将src字符串拼接到dest所指的字符串尾部。
返回值:返回dest字符串起始地址。
dest最后原有的结尾字符0会被覆盖掉,并在连接后的字符串的尾部再增加一个0。
如果参数dest所指的内存空间不够大,会导致数组的越界。
字符串比较strcmp()和strncmp()
int strcmp(const char *str1, const char *str2 );
功能:比较str1和str2的大小。
返回值:相等返回0,str1大于str2返回1,str1小于str2返回-1;
int strncmp(const char *str1,const char *str2 ,const size_t n);
功能:比较str1和str2前n个字符的大小。
返回值:相等返回0,str1大于str2返回1,str1小于str2返回-1;
两个字符串比较的方法是比较字符的ASCII码的大小,从两个字符串的第一个字符开始,如果分不出大小,就比较第二个字符,如果全部的字符都分不出大小,就返回0,表示两个字符串相等。
在实际开发中,程序员一般只关心字符串是否相等,不关心哪个字符串更大或更小。
查找字符strchr()和strrchr()
const char *strchr(const char *s, int c);
返回在字符串s中第一次出现c的位置,如果找不到,返回0。
const char *strrchr(const char *s, int c);
返回在字符串s中最后一次出现c的位置,如果找不到,返回0。
查找字符串strstr()
char *strstr(const char* str,const char* substr);
功能:检索子串在字符串中首次出现的位置。
返回值:返回字符串str中第一次出现子串substr的地址;如果没有检索到子串,则返回0。
指针变量简称指针,它是一种特殊的变量,专用于存放变量在内存中的起始地址。
语法:数据类型 *变量名;
数据类型必须是合法的C++数据类型(int、char、double或其它自定义的数据类型)。
星号*与乘法中使用的星号是相同的,但是,在这个场景中,星号用于表示这个变量是指针。
不管是整型、浮点型、字符型,还是其它的数据类型的变量,它的地址都是一个十六进制数。我们用整型指针存放整数型变量的地址;用字符型指针存放字符型变量的地址;用浮点型指针存放浮点型变量的地址,用自定义数据类型指针存放自定义数据类型变量的地址。
语法:指针=&变量名;
注意
指针也是变量,是变量就要占用内存空间。
在64位的操作系统中,不管是什么类型的指针,占用的内存都是8字节。
在C++中,指针是复合数据类型,复合数据类型是指基于其它类型而定义的数据类型,在程序中,int是整型类型,int*是整型指针类型,int*可以用于声明变量,可以用于sizeof运算符,可以用于数据类型的强制转换,总的来说,把int*当成一种数据类型就是了。
声明指针变量后,在没有赋值之前,里面是乱七八糟的值,这时候不能使用指针。
指针存放变量的地址,因此,指针名表示的是地址(就像变量名可以表示变量的值一样)
*运算符被称为间接值或解除引用(解引用)运算符,将它用于指针,可以得到该地址的内存中存储的值,*也是乘法符号,C++根据上下文来确定所指的是乘法还是解引用。
如果把函数的形参声明为指针,调用的时候把实参的地址传进去,形参中存放的是实参的地址,在函数中通过解引用的方法直接操作内存中的数据,可以修改实数的值,这种方法被通俗的称为地址传递或传地址。
值传递:函数的形参是普通变量。
传地址的意义如下:
语法:const 数据类型 *变量名;(左定值)
不能通过解引用的方法修改内存地址中的值(用原始的变量名是可以修改的)。
注意:
如果形参的值不需要改变,建议加上const修饰,程序可读性更好。
语法:数据类型 * const 变量名;
指向的变量(对象)不可改变。
注意:
C++编译器把指针常量做了一些特别的处理,改头换面之后,有一个新的名字,叫引用。
如果对空指针解引用,程序会崩溃。
如果对空指针使用delete运算符,系统将忽略该操作,不会出现异常。所以,内存被释放后,也应该把指针指向空。
在函数中,应该有判断形参是否为空指针的代码,目的是保证程序的健壮性。
为什么空指针访问会出现异常?
NULL指针分配的分区:其范围是从 0x00000000到0x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面NULL指针分区。
野指针就是指针指向的不是一个有效(合法)的地址。
在程序中,如果访问野指针,可能会造成程序的崩溃。
出现野指针的情况主要有三种:
1)指针在定义的时候,如果没有进行初始化,它的值是不确定的(乱指一气)。
2)如果用指针指向了动态分配的内存,内存被释放后,指针不会置空,但是,指向的地址已失效。
3)指针指向的变量已超越变量的作用域(变量的内存空间已被系统回收),让指针指向了函数的局部变量,或者把函数的局部变量的地址作为返回值赋给了指针。
规避方法:
1)指针在定义的时候,如果没地方指,就初始化为nullptr。
2)动态分配的内存被释放后,将其置为nullptr。
3)函数不要返回局部变量的地址。
注意:野指针的危害比空指针要大很多,在程序中,如果访问野指针,可能会造成程序的崩溃。是可能,不是一定,程序的表现是不稳定,增加了调试程序的难度。
在C++中,void表示为无类型,主要有三个用途:
void func(int a,int b)
{
// 函数体代码。
return;
}
int func(void)
{
// 函数体代码。
return 0;
}
注意:
使用堆区的内存有四个步骤:
1)声明一个指针;
2)用new运算符向系统申请一块内存,让指针指向这块内存;
3)通过对指针解引用的方法,像使用变量一样使用这块内存;
4)如果这块内存不用了,用delete运算符释放它。
申请内存的语法:new 数据类型(初始值); // C++11支持{}
如果申请成功,返回一个地址;如果申请失败,返回一个空地址(暂时不考虑失败的情况)。
释放内存的语法:delete 地址;
释放内存不会失败(还钱不会失败)。
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
// 1)声明一个指针;
// 2)用new运算符向系统申请一块内存,让指针指向这块内存;
// 3)通过对指针解引用的方法,像使用变量一样使用这块内存;
// 4)如果这块内存不用了,用delete运算符释放它。
// 申请内存的语法:new 数据类型(初始值); // C++11支持{}
// 释放内存的语法:delete 地址;
int* p = new int(5);
cout << "*p=" << *p << endl;
*p = 8;
cout << "*p=" << *p << endl;
delete p;
/* for (int ii = 1; ii > 0; ii++)
{
int* p = new int[100000]; // 一次申请100000个整数,这个语法以后再讲。
cout << "ii="<
将一个整型变量加1后,其值将增加1。
但是,将指针变量(地址的值)加1后,增加的量等于它指向的数据类型的字节数。
a)数组在内存中占用的空间是连续的。
b)C++将数组名解释为数组第0个元素的地址。
c)数组第0个元素的地址和数组首地址的取值是相同的。
d)数组第n个元素的地址是:数组首地址+n
e)C++编译器把 数组名[下标] 解释为 *(数组首地址+下标)
数组是占用连续空间的一块内存,数组名被解释为数组第0个元素的地址。C++操作这块内存有两种方法:数组解释法和指针表示法,它们是等价的。
在多数情况下,C++将数组名解释为数组的第0个元素的地址,但是,将sizeof运算符用于数据名时,将返回整个数组占用内存空间的字节数。
可以修改指针的值,但数组名是常量,不可修改。
普通数组在栈上分配内存,栈很小;如果需要存放更多的元素,必须在堆上分配内存。
动态创建一维数组的语法:数据类型 *指针=new 数据类型[数组长度];
释放一维数组的语法:delete [] 指针;
注意:
#include // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int compasc(const void* p1, const void* p2) // 升序的回调函数。
{
return *((int*)p1) - *((int*)p2);
}
int compdesc(const void* p1, const void* p2) // 降序的回调函数。
{
return *((int*)p2) - *((int*)p1);
}
int main()
{
int a[8] = { 4,2,7,5,8,6,1,3 };
qsort(a,sizeof(a)/sizeof(int),sizeof(int),compasc); // 对数组a进行升序排序。
for (int ii = 0; ii < 8; ii++)
{
cout << "a[" << ii << "]=" << a[ii] << endl;
}
qsort(a, sizeof(a) / sizeof(int), sizeof(int), compdesc); // 对数组a进行降序排序。
for (int ii = 0; ii < 8; ii++)
{
cout << "a[" << ii << "]=" << a[ii] << endl;
}
}
共同体(共用体、联合体)是一种数据格式,它能存储不同的数据类型,但是,在同一时间只能存储其中的一种类型。
声明共同体的语法:
union 共同体名
{
成员一的数据类型 成员名一;
成员二的数据类型 成员名二;
成员三的数据类型 成员名三;
......
成员n的数据类型 成员名n;
};
注意:
应用场景:
如果引用的数据对象类型不匹配,当引用为const时,C++将创建临时变量,让引用指向临时变量。
什么时候将创建临时变量呢?
结论:如果函数的实参不是左值或与const引用形参的类型不匹配,那么C++将创建正确类型的匿名变量,将实参的值传递给匿名变量,并让形参来引用该变量。
将引用形参声明为const的理由有三个:
左值是可以被引用的数据对象,可以通过地址访问它们,例如:变量、数组元素、结构体成员、引用和解引用的指针。
非左值包括字面常量(用双引号包含的字符串除外)和包含多项的表达式。
传统的函数返回机制与值传递类似。
函数的返回值被拷贝到一个临时位置(寄存器或栈),然后调用者程序再使用这个值。
double m=sqrt(36); // sqrt()是求平方根函数。
sqrt(36)的返回值6被拷贝到临时的位置,然后赋值给m。
cout << sqrt(25);
sqrt(25)的返回值5被拷贝到临时的位置,然后传递给cout。
如果返回的是一个结构体,将把整个结构体拷贝到临时的位置。
如果返回引用不会拷贝内存。
语法:
返回值的数据类型& 函数名(形参列表);
注意:
1)如果不需要在函数中修改实参
2)如果需要在函数中修改实参