2022.01.22 C++ 基础(四)(字符串、指针、引用)

1、C++ 字符

字符与整数密切相关,因为它们在内部其实是被存储为整数。每个可打印的字符以及许多不可打印的字符都被分配一个唯一的数字。用于编码字符的最常见方法是 ASCII(美国信息交换标准代码的首字母简写)。当字符存储在内存中时,它实际上是存储的数字代码。当计算机被指示在屏幕上打印该值时,它将显示与数字代码对应的字符。
在这里插入图片描述

#include <iostream>
using namespace std;
int main()
{
    char letter;
    letter = 65; // letter = 'A';    65 is the ASCII code for the character A  
    cout << letter << endl;
    letter = 66; // letter = 'B';    66 is the ASCII code for the character B
    cout << letter << endl;
    return 0;
}
  • 整数转换为字符
char num = 5 + '0';		// char num = 5 + 48;  48 is the ASCII code for the character 0  

char [] 与 char *

#include<iostream>
using namespace std;
int main()
{
    const char *p1 = "abcd";
    char p2[] = "1234";
    return 0;
}

这二者的区别还在于:

  1. p1是一个指针变量,有一块内存存储它,它的内容是字符串的地址,那么我们要访问字符串就先要取出p1中存储的地址,然后计算偏移量,进行访问;
  2. 不同于p1,p2直接是字符串的地址,直接访问就行了。

“abcd”是常量存储区分配了内存存储的,栈上分配一地址给p1并指向“abcd”,那么如果在后面的代码中改变了“abcd”,自然崩溃。所以,需要加上const限定;

但是说到底,为什么改变p1就是危险的,字符数组的内容就能随意改变呢?这是因为 abcd是在编译时刻就确定的,而 1234 是在运行时刻赋值的。所以,编译器在编译时就已经知道p1指向的是常量,他并不希望你改变,但是数组不同,可以说他只是个存储的工具,编译器编译时并不知道它里面是什么。

但在往后的存取中,在栈上的数组比指针所指向的字符串是要快的。
c++内存被分为5个区,分别是堆、栈、自由存储区、全局/静态存储区和常量存储区

int a=0;    //全局初始化区
char *p1; 	//全局未初始化区
main()
{
 	int b;                  //栈
    char s[]="abc";  		//栈
    char *p2;           	//栈
    char *p3 = "123456";   	//123456\0在常量区,p3在栈上。
    static int c=0;     	//全局(静态)初始化区
    p2 = (char*)malloc(20); //分配得来得10和20字节的区域就在堆区。
    strcpy(p1,"123456");   	//123456\0放在常量区,编译器可能会将它与p3所向"123456"优化成一个地方。
 }
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << (str1 == str2) << endl; 	// 0
cout << (str3 == str4) << endl;		// 0
cout << (str5 == str6) << endl;		// 1
cout << (str7 == str8) << endl;		// 1

数据内存分布情况分析:

char str1[] = "abc"

  这里的"abc"是一个常量,首先会在常量存储区里存储"abc"这个常量,然后会因为"abc"被赋值给str1[],所以在栈中开辟一段内存,内存大小为4个节点(char数组后会自动加一个'\0'),然后又有一个"abc"被保存在栈中。

  同理,str2[]中的"abc"也是保存在栈中,地址不同。

  到此,有三个"abc"被保存起来,一个在常量存储区,另外两个在栈中。

  此外,插一句,c++内存被分为5个区,分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

const char str3[] = "abc"

  对于这种被const修饰起来的变量,一般也是被保存在常量存储区,但是,但是对于const数组来讲,系统不确定符号表是否有足够的空间来存放const数组,所以还是为const数组分配内存的。所以str3指向的是栈上的"abc"。

  同理,str4[]也是保存在栈中,地址不同。

const char *str5 = "abc":

  因为"abc"在常量存储区中保存有一份(即使没保存,这样的操作也会新建一份),这里str5定义的时候,嘿,我见过这个,str5就可以开心的直接指向"abc"所在的常量区的地址。

  同理str6,str7,str8。与const没有半毛钱关系,const只是使得str5和str6无法指向新的字符串常量(也就是新的地址)。

2.1、C++ 字符串

字符常数和 char 变量只能保存一个字符。如果要在常数或变量中存储多个字符,则需要使用更复杂的字符数据类型 string(字符串),字符串常数和变量可以包含一系列的字符。

C 风格的字符串起源于 C 语言,并在 C++ 中继续得到支持。字符串实际上是使用 null 字符 \0 终止的一维字符数组。(不需要把 null 字符放在字符串常量的末尾。C++ 编译器会在初始化数组时,自动把 \0 放在字符串的末尾。)

不要将空终止符与字符 ‘0’ 混淆。字符 ‘0’ 的 ASCII 码是 48,而空终止符的 ASCII 码是 0。如果要在屏幕上打印字符 0,则显示的其实是 ASCII 码为 48 的字符;在使用字符串常数或给字符串变量赋值时,ASCII 代码为 0 的字符将自动附加在其末尾。

  • C风格字符串声明:
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};

等同于

char site[] = "RUNOOB";
  • C++ String类 < string >
#include <string>
string str = "runoob";
  1. 和许多 STL 容器相同,string 能动态分配空间,这使得我们可以直接使用 std::cin 来输入;
  2. string 重载了加法运算符,可以直接拼接两个字符串或一个字符串和一个字符;
  3. string 重载了比较运算符,同样是按字典序比较的,所以我们可以直接调用 std::sort 对若干字符串进行排序。
  • strlen、sizeof

1、sizeof 操作符的结果类型是 size_t,它在头文件中 typedef 为 unsigned int 类型。该类型保证能容纳实现所建立的最大对象的字节大小。
2、sizeof 是运算符,strlen 是函数。
3、sizeof 可以用类型做参数,strlen 只能用 char* 做参数,且必须是以 \0 结尾的。

char* ss = "0123456789";
sizeof(ss) //结果 4 ===》ss 是指向字符串常量的字符指针,sizeof 获得的是一个指针的之所占的空间,应该是长整型的,所以是 4。
sizeof(*ss) //结果 1 ===》*ss 是第一个字符 其实就是获得了字符串的第一位 '0' 所占的内存空间,是 char 类型的,占了 1 位
strlen(ss)  // 结果 10  ===》 如果要获得这个字符串的长度,则一定要使用 strlen。strlen 用来求字符串的长度;而 sizeof 是用来求指定变量或者变量类型等所占内存大小。

1. 转char数组(char*)

const char *p = str.data()
const char *p = str.c_str() 保证末尾有空字符

2. 获取长度

printf("s 的长度为 %lu", s.size());
printf("s 的长度为 %lu", s.length());
printf("s 的长度为 %lu", strlen(s.c_str()));

这三个函数(以及下面将要提到的 find 函数)的返回值类型都是 size_t(unsigned long)。因此,这些返回值不支持直接与负数比较或运算,建议在需要时进行强制转换

3. 寻找第一次出现位置

find(str,pos) 函数可以用来查找字符串中一个字符/字符串在 pos(含)之后第一次出现的位置(若不传参给 pos 则默认为 0)。
如果没有出现,则返回 string::npos(被定义为 -1,但类型仍为 size_t/unsigned long)。

4. 截取子串

substr(pos, len) 函数的参数返回从 pos 位置开始截取最多 len 个字符组成的字符串(如果从 pos 开始的后缀长度不足 len 则截取这个后缀)。

5. 插入/删除字符(串)

  1. insert(index,count,ch)insert(index,str) 分别表示在 index 处连续插入 count 次字符串 ch 和插入字符串 str。
  2. erase(index,count) 函数将字符串 index 位置开始(含)的 count 个字符删除(若不传参给 count 则表示删去 count 位置及以后的所有字符)。

6. 替换字符(串)

replace(pos,count,str) 表示将从 pos 位置开始 count 个字符的子串替换为 str
replace(first,last,str) 表示将以 first 开始(含)、last 结束(不含)的子串替换为 str,其中 first 和 last 均为迭代器

7. STL 对 string 类操作

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
int main()
{
    string s("afgcbed");
    string::iterator p = find(s.begin(), s.end(), 'c');		// 迭代器p
    if (p!= s.end())
        cout << p - s.begin() << endl;  //输出 3
    sort(s.begin(), s.end());					// 排序
    cout << s << endl;  //输出 abcdefg
    next_permutation(s.begin(), s.end());		// 全排列函数
    cout << s << endl; //输出 abcdegf
    return 0;
}

字符串常用函数:https://www.cnblogs.com/lemaden/p/10120660.html

2.2、字符串转换

1. char转string

  • 定义一个空string,调用append()push_back()方法
char c = 'c';
string s = "";
s.append(1, c);
// s.push_back(c);
  • 使用stringstream,注意添加相应头文件<sstream>
#include <sstream>
stringstream stream;
char c = 'c';
stream << c;
cout << stream.str() == "c" ? true : false;		// true
  • 使用string构造函数
// string(size_t,char)
char c = 'c';
string s(1, c);
  • 错误用法
char c = 'c';
cout << to_string(c) == "c" ? true : false << endl;	// false

to_string是将 char 字符的ASCII码值转换成了字符串

2. string转数字

  • 使用stringstream
#include<sstream>
int num;
string s = "123";
stringstream stream;
stream << s;
stream >> num;
cout << num << endl;
  • 库函数stoi、stol、stof、stod
string s;
int a = stoi(s);
long b = stol(s);
float c = stof(s);
double d = stod(s);
  • 利用字符转数字的方法s[i]-'0',对字符串进行字符遍历,并乘以相应的倍数(个十百千…)
float str2num(){
    float num;
    string s = "3.14159";
    int posdot = s.find('.');   // 小数点所在下标索引
    // 整数部分
    for(int i = 0; i < posdot; ++i){
        num =  num * 10 + (s[i] - '0');
    }
    // 小数部分
    float temp = 0;
    for(int i = s.length()-1; i > posdot; --i){
        temp = temp * 0.1 + (s[i] - '0') * 0.1;
    }
    num += temp;    // 整数、小数相加
    return num;
}

3. 数字转字符串

  • to_string()
int num = 123;
string s;
s = to_string(num);
cout << s << endl;
  • 使用stringstream
#include<sstream>
int num = 123;
stringstream stream;
stream << num;
cout << stream.str() << endl;
  • 利用数字转字符的方法s[i] + '0',除以%10取模运算
string my_int2str(int num){
    int temp = num < 0 ? -num: num;     // temp为num的绝对值
    char buf[10] = "";
    int i = 0;
    while(temp){                        
        buf[i++] = (temp % 10) + '0';   // 把temp的每一位上的数存入buf
        temp = temp / 10;               // 得到的buf为倒序
    }
    int len = num < 0? ++i : i;         // 字符串长度,小于0时多一个负号的位置
    char str[len+1] = "";               // 输出的字符串,长度+1,存放结束符(空指针)
    while(1){
        --i;
        if(buf[len-i-1] == 0) break;    
        str[i] = buf[len-i-1]; 			// buf 倒序赋值str
    } 
    if(i == 0) str[i] = '-';            // i=0 代表预留了负号位置,否则i=-1
    return str;
}

3、C++ 指针

  • 指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。
  • 通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。
  • 指针变量声明:type *var-name;
#include <iostream>
using namespace std;
int main()
{
  int *p;
  cout << p << endl;    //0x401b40
  int var = 50;
  p = &var;
  cout << &var << endl; //0x61ff18
  cout << p << endl;    //0x61ff18
  cout << *p << endl;   //50
}
  • 声明指向函数的指针,被称为函数指针data_types (*func_pointer)( data_types arg1, data_types arg2, ...,data_types argn);
int a(int b);
{
    cout<<b;
    return ++b;
}

int(*p)(int);
p=a;

(*p)(5); //指针调用函数
  • NULL指针 int *ptr = NULL;(内存地址为 0 有特别重要的意义,它表明该指针不指向一个可访问的内存位置。)
  • 指针的运算:++、–、+、-、==、>、<
  • 指针数组 与 数组指针

指针数组:元素为指针变量的一维数组int* (arr[3])int* arr[3]
数组指针:指向多维数组地址的指针int (*ptr)[2]
https://www.runoob.com/cplusplus/cpp-array-of-pointers.html

#include <iostream>
using namespace std;
 
int main()
{
  cout << "指针数组--元素为指针变量的一维数组。" << endl;
  int* arr[3];      // 指针数组:元素为指针变量的一维数组。 等价于 int* (arr[3]);
  int  p[] = {1,2,3};
  for (int i = 0; i < 3; ++i)
  {
    arr[i] = &p[i];
  }
  cout << arr << endl << endl;  //指针数组的地址
  for (int i = 0; i < 3; ++i)
  {
    cout << arr+i << endl;
    cout << *(arr+i) << endl;
    cout << arr[i] << endl;
    cout << *arr[i] << endl;
  }


  cout << endl << endl << "数组指针--指向多维数组地址的指针。" << endl;
  int pp[2][2] = {
                  {1,2},
                  {3,4}
                  };    // pp是个二维数组的数组名,相当于一个二级指针常量
  int (*ptr)[2] = pp;  //  数组指针:指向多维数组地址的指针
  for (int i = 0; i < 2; ++i)
  {
    cout << ptr + i << endl;  // ptr+i 是一维数组pp[i]的地址,即ptr+i==&pp[i];
    cout << &pp[i] << endl;
    cout << **(ptr + i)<< endl; // 二级指针 **(ptr + i) = pp[i][0];
    cout << pp[i][0]<< endl; 
  }
  
  cout << *(*(ptr+1)+1) << endl; // 二级指针 *(*(ptr+1)+1) = pp[1][1];
  cout << pp[1][1] << endl;
}

用一个指向字符的指针数组来存储一个字符串列表

#include <iostream>
using namespace std;
/**
 * 用一个指向字符的指针数组来存储一个字符串列表
 * Value of names[0] = sun;
 */
int main(){
    const char *names[3] = {
            "sun","bin","sunbin"
    };
    for(int i = 0;i < 3;i++){
        cout <<"Value of names[" << i << "] = ";//输出字符串的值
        cout << names[i] << endl;
        cout <<"Value of *names[" << i << "] = ";//输出指针所指向字符串首地址的值
        cout << *names[i] << endl;
        cout <<"Value of *names[" << i << "]+1 = ";//输出ascii码值
        cout << *names[i]+1 << endl;
        cout <<"Value of *(names[" << i << "]+1) = ";//输出指针所指向字符串首地址上一位的值
        cout << *(names[i]+1) << endl;
    }
}

字符串三级指针

#include <iostream>
using namespace std;
int main(){
 	const char *c[]={"HELLO","NEW","WORLD","SAYHI"};  // c数组存的是分别指向四个字符串的指针
    cout << c << endl;          // 输出指向首个字符串的指针  &"HELLO"
    cout << *c << endl;         // 输出首个字符串内容 *c = c[0] = "HELLO"
    cout << **c << endl;        // 输出首个字符串的首个字符 'H' = *c[0]
    cout << c + 1 << endl;      // 字符串"NEW"的地址
    cout << c + 2 << endl;      // 字符串"WORLD"的地址
    cout << c + 3 << endl << endl;  // 字符串"SAYHI"的地址

    // cp存储的是地址的地址 { &&"SAYHI", &&"WORLD", &&"NEW", &&"HELLO" }
    // 因为cp本身指向地址,又定义为char **,所以cp是三级指针。
    const char **cp[]={c+3, c+2, c+1, c}; 
    cout << cp << endl;         // & c+3
    cout << *cp << endl;        // &"SAYHI"  =  c+3 = cp[0]
    cout << **cp << endl;       // "SAYHI"  
    cout << ***cp << endl << endl;      // 'S' 

    // 将三级指针cp赋给cpp,则cpp存的是cp的地址。
    const char ***cpp = cp;           
    cout << cpp << endl;        // & c+3
    cout << *cpp << endl;       // &"SAYHI" = c+3 = &c[3] = cp[0]
    cout << **cpp << endl;      // "SAYHI"  
    cout << ***cpp << endl << endl;     // 'S'

    printf("%s,",**++cpp);  
    // ++cpp 得到cp数组第二个元素c+2的地址cp+1,结果即为 **(c+2) = "WORLD"
    // 此时cpp = cp+1

    printf("%s,",*--*++cpp + 3);      
    // ++cpp 得到cp数组第三个元素c+1的地址 cp+2
    // 此时cpp = cp+2
    // *++cpp得到c+1
    // --*++cpp得到c
    // *--*++cpp得到“HELLO”首地址
    // *--*++cpp+3得到“LO”首地址

    printf("%s,",*cpp[-2]+3);      
    // cpp[-2] 得到cp数组第一个元素的值c+3
    // *cpp[-2]得到“SAYHI”首地址
    // *cpp[-2]+3得到“HI”首地址

    printf("%s\n",cpp[-1][-1]+1);
    // cpp[-1] = *(cpp-1) 得到cp数组第二个元素的值c+2
    // cpp[-1][-1] = *(c+2-1)得到“NEW”
    // cpp[-1][-1]+1得到“EW”
    return 0;
}

4、C++ 引用

引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

引用 VS 指针
  • 不存在空引用。引用必须连接到一块合法的内存。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值