C++字符串、向量和数组4--数组

本文深入探讨数组和vector两种数据结构的特性与区别,讲解数组的定义、初始化、访问及与指针的关系,同时对比vector的动态特性,强调现代C++编程中应优先选择vector和迭代器。

   数组是一种类似于标准库vector的数据结构,与vector相同的是,数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问。与vector不同的是,数组的大小确定不变,不能随意向数组中增加元素。因为数组的大小固定,因此对于某些特殊的应用程序来说程序的运行时性能较好,但是相应的也损失了一些灵活性。

1.定义和初始化内置数组

   数组的声明形式如a[d],其中a是数组的名字,d是数组的维度。维度说明了数组的个数,因此必须大于0,必须是一个常量表达式。

unsigned int cnt = 42;      //不是常量表达式
constexpr unsigned sz = 42; //是常量表达式
int arr[10];                //含有10个整数的数组
int *parr[sz];              //含有42个整数指针的数组
string bad[cnt];            //错误,cnt不是常量表达式
string strs[get_size()];    //当get_size()是constexpr时正确,否则错误

   定义数组的时候必须指定数组的类型,不允许使用auto关键字由初始值列表推导类型。另外和vector一样,数组的元素应为对象,因此不存在引用的数组。

显示初始化数组
   可以对数组的元素进行列表初始化,此时允许忽略数组的维度。如果在声明时没有指明维度,编译器会根据初始值的数量计算出来;相反,如果指明了维度,那么初始值的总数不应该超出指定的大小。如果维度比提供的初始值数量大,则用提供的初始值初始化靠前的元素,剩下的元素被初始化成默认值:

const unsigned sz = 3;    
int ia1[sz] = {0, 1, 2};   //含有3个元素的数组,元素值分别是0,1,2
int ia2[] = {0, 1, 2};     //维度是3的数组
int ia3[5] = {0, 1, 2};    //等价于a3[] = {0,1,2,0,0}
string sa4[3] = {"hi", "bye"};  //等价于sa4 = {"hi", "bye", ""}
int ia5[2] = {0, 1, 2};  //错误,初始值过多

字符数组的特殊性
   字符数组有一种额外的初始化形式,我们可以用字符串字面值对此类数组初始化。当使用这在形式时,一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其他字符一样被拷贝到字符数组中去:

char a1[] = {'C', '+', '+'};   //列表初始化,没有空字符
char a2[] = {'C', '+', '+', '\0'}; //列表初始化,含有显示空字符
char a3[] = "C++";     //自动添加表示字符串结束的空字符,数组大小是4
const char a4[6] = "Daniel";  //错误,没有空间可存放空字符

不允许拷贝和赋值
   不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值:

int a[] = {0, 1, 2};  //含有3个整数的数组
int a2[] = a;   //错误,不允许使用一个数组初始化另外一个数组
a2 = a;     //错误,不允许把一个数组直接赋值给另外一个数组

   一些编译器支持数组的赋值,这就是所谓的编译器扩展。但一般来说,最好避免使用非标准特性,因为含有非标准特性的程序可能在其他编译器上无法正常工作。

理解复杂的数组声明
   和vector一样,数组允许定义数组的指针以及数组的引用:

int *ptrs[10];	      //ptrs是含有是个整形指针的数组,是一个数组
int &refs[10] = "";   //错误,不存在引用的数组
int (*parray)[10] = &arr; //parray是一个指向10个整形数组的指针, 是一个指针
int (&arrRef)[10] = arr;  //arrRef引用一个含有10个整形的数组
int *(&array)[10] = ptrs; //array是一个含有10个整形指针的数组的引用

   要理解数组声明的含义,最好的办法就是从数组的名字开始由内往外的顺序阅读,比如array,首先array是一个引用,然后观察右边知道array引用的对象是一个大小为10的数组,最后观察左边知道,数组的元素类型是指向int的指针。这样,array就是一个含有10个int指针的数组的引用。

2.访问数组元素

   与标准库vector一样,数组的元素也能使用范围for语句或者下标运算符来访问。使用下标运算符的时候,通常将其定义为size_t。size_t是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小,在cstddef文件中定义了size_t类型。
   和vector一样,使用下标时应该检查下标的合理性,合理的下标大小应该是大于0而小于数组的大小。超出这个范围的下标会导致数组越界,使程序崩溃。

3.指针和数组

   数组和指针有非常紧密的联系,使用数组的时候编译器一般会把它转换成指针。对数组的元素使用取地址符就能得到指向该元素的地址:

string nums[] = {"one", "two", "three"};
string *p = &nums[0];   //p指向nums中的第一个元素
string *p = nums;   // 等价于string *p = &nums[0]; 

   很多使用到数组名的地方,编译器会自动将其替换为一个指向数组首元素的指针。
   使用数组作为一个auto的变量的初始值时,推导得到的是指针而非数组:

int ia = {0,1,2,3};
auto ia2(ia);   //ia2是一个指针,指向ia的第一个元素
ia2 = 42;	   //错误,ia2是一个指针,不能用int给指针赋值

   当使用decltype关键字时上述转换不会发生,decltype(ia)返回的类型是由4个整数构成的数组:

decltype(ia) ia3 = {0,1,2,3};  
ia3 = p;  //错误,不能用整型指针给数组赋值
ia3[2] = 1;  //正确,把1赋值给ia3的第3个元素

指针也是迭代器
   vector的迭代器支持的运算,数组的指针全部支持,包括指针递增递减运算,使用指针遍历数组元素:

int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *p = arr;    //p指向arr的第一个元素的指针
++p;        //p指向arr[1]
int *e = &arr[10];   //e指向arr尾元素的下一位置的指针,不能对尾后指针指向解引用和递增操作

标准库begin和end
   为了让指针的使用更简单,更安全,C++11新标准引入了两个名为begin和end的函数。这两个函数与容器中的两个同名成员功能类似,不过这两个函数不是成员函数:

int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(arr);   //指向arr的首元素的指针
int *last = end(arr);    //指向arr尾元素的下一位置的指针

指针运算
   给一个指针加上或减去某个整数值,结果仍然是指针,新指针指向的元素与原来的指针相比前进或后退了该整数值的为位置。
   两个指针相减的结果是它们之间的距离。参与运算的两个指针必须指向同一个数组中的元素。相减的结果是一种名为ptrdiff_t 的标准类型,是一种带符号类型。

4.C风格字符串

   C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。字符串放在字符数组中并以空字符结束。
   C风格字符串函数:

函数说明
strlen§返回p的长度,空字符不计算在内
strcmp(p1,p2)比较p1和p2的相等性,如果p1==p2则返回0,大于返回整数,小于返回负数
strcat(p1,p2)将p2附加到p1之后,并返回p1
strcpy(p1,p2)将p2拷贝到p1,并返回p1

   传入此类的指针必须指向以空字符作为结束的数组:

char ca[] = {'C', '+', '+'};
cout << strlen(ca) << endl;

   ca虽然是一个数组但它不是以空字符结束,因此上述程序将产生未定义的结果。strlen有可能沿着ca在内存中的位置不断向前寻找,直到遇到空字符才停止。

   对大多数应用来说,使用标准库cstring要比使用C风格字符串更安全高效。

5.与旧代码的接口

   很多C++程序在标准库出现之前就已经写成,它们肯定没有想到vector和string的类型。而且,有一些C++程序实际是与C语言或其他语言的接口程序,当然也无法使用C++标准库。因此,现代C++程序不得与那些充满数组或C风格字符串的代码衔接,为了使这一工作简单易行,C++专门提供来一组功能。

混用string对象和C风格字符串
   任何出现字符串字面值的地方都可以用以空字符结束的字符数组替代:
   允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值。
   在string对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是)。在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象。
   上述性质反过来就不成立了: 如果程序某处需要一个C风格字符串,无法直接使用string对象来替代它,string专门提供了一个名为c_str的成员函数:

char *str = s;    //错误,不能用string对象初始化char *
const cahr *str = s.c_str();   //正确

   我们无法保证c_str函数返回的数组一直有效,事实上,如果后续的操作改变了s的值就可能让之前返回的数组失去效用。如果执行完c_str函数后程序想一直都能使用其返回值,最好将该数组重新拷贝一份。

使用数组初始化vectr对象
   允许使用数组来初始化vector对象,要想实现这一目的,只需要指明拷贝区的首元素地址和尾元素地址就可以了:

int arr[] = {0,1,2,3,4,5};
vector<int> ivec(begin(arr), end(arr)); // ivec中有6个元素,分别是arr数组中元素的副本
vector<int> ivec2(arr+1, arr+4); //拷贝3个元素arr[1],arr[2],arr[3]

尽量使用标准库而非数组
   使用指针和数组很容易出错,一部分原因是概念上的问题:指针常用于底层操作,其他问题则源于语法错误,特别是指针声明时的错误。
   现代C++程序应尽量使用vector和迭代器,避免使用内置数组和指针。应该使用string,避免使用C风格基于数组的字符串。

<think> 根据用户的问题,用户需要了解C++字符串二维数组的输入输出方法。结合引用内容,我注意到引用[1]和引用[3]提供了相关的方法。特别是引用[3]中有一个示例函数`stringToIntegerMatrix1`,它处理的是整数二维数组,但我们可以借鉴其思路来处理字符串二维数组。 用户要求的是字符串二维数组,所以需要存储的是`string`类型。我们可以考虑以下情况: 1. 输入格式:用户可能以不同的格式输入,比如每行一个字符串数组,用空格分隔,然后多行输入;或者输入一个类似`[[str1,str2],[str3,str4]]`的格式(如引用[3]中的整数矩阵输入)。 2. 输出格式:相应地输出二维字符串数组。 但是,用户并没有指定输入格式,所以我们需要提供常见的几种输入方式。 根据引用[1]中的内容,有几种输入方式: - 输入多个字符串,每个串之间以空格作为结束;最终结束是回车(即一行字符串,空格分隔,回车结束一行,然后可以有多行) - 输入不定矩阵,以回车结尾(即行数不定,每行字符串数也不定?) 另外,引用[3]提供了一种解析特定格式字符串的方法(例如`[[1,2,3],[4,5,6]]`),我们可以将其扩展到字符串。 因此,我将提供两种常见情况的示例: 情况1:逐行输入,每行由空格分隔的多个字符串成,直到遇到空行或结束标志。 情况2:输入一个类似`[["str1","str2"],["str3","str4"]]`的字符串,然后解析成二维字符串数组。 同时,输出时,我们可以按照行输出,每行内的字符串用空格分隔,或者按照类似二维数组的格式输出。 由于用户要求输入输出方法,我将分别给出输入和输出的代码示例。 注意:在C++中,字符串二维数组通常用`vector<vector<string>>`来表示,这样更灵活。 方案1:逐行输入,每行用空格分隔多个字符串,直到输入空行(或指定行数) 方案2:解析特定格式的字符串(如JSON格式,但这里简化处理) 下面分别给出两种方案的代码: 方案1:逐行输入,直到遇到空行(或者也可以指定行数) 输入示例: hello world this is a test (然后输入一个空行结束) 代码: ```cpp #include <iostream> #include <vector> #include <string> #include <sstream> using namespace std; int main() { vector<vector<string>> str2DArray; string line; cout << "请输入字符串矩阵,每行用空格分隔多个字符串,空行结束:" << endl; while (getline(cin, line)) { if (line.empty()) // 空行结束输入 break; vector<string> row; stringstream ss(line); string word; while (ss >> word) { row.push_back(word); } str2DArray.push_back(row); } // 输出二维字符串数组 cout << "输出结果:" << endl; for (const auto& row : str2DArray) { for (const auto& s : row) { cout << s << " "; } cout << endl; } return 0; } ``` 方案2:解析特定格式的字符串,如`[["str1","str2"],["str3","str4"]]`。注意,这个解析相对复杂,因为字符串中可能包含逗号、空格等。我们假设输入的字符串格式是标准的,每个字符串用双引号括起来,且字符串内不包含双引号和逗号(或者我们可以用转义,但这里简化处理)。 我们参考引用[3]中的整数矩阵解析方法,修改为处理字符串。 输入示例:`[["hello","world"],["this","is","a","test"]]` 代码: ```cpp #include <iostream> #include <vector> #include <string> #include <sstream> #include <cctype> using namespace std; vector<vector<string>> parseStringMatrix(string input) { vector<vector<string>> matrix; // 去除输入字符串首尾的方括号 if (input.front() == '[') input = input.substr(1, input.size()-2); else return matrix; vector<string> row; string token; bool inQuotes = false; bool inBracket = false; // 是否在行向量中(即[ ... ]内部) for (size_t i=0; i<input.size(); ) { if (input[i]=='[') { inBracket = true; row.clear(); i++; } else if (input[i]==']') { inBracket = false; // 将当前行加入矩阵 matrix.push_back(row); row.clear(); // 跳过逗号和可能的空格 i++; while (i<input.size() && (input[i]==',' || input[i]==' ')) i++; } else if (inBracket && input[i]=='"') { // 开始一个字符串 inQuotes = true; token = ""; i++; while (i<input.size() && inQuotes) { if (input[i]=='\\') { // 转义字符 if (i+1 < input.size()) { token += input[i+1]; i += 2; } } else if (input[i]=='"') { inQuotes = false; i++; } else { token += input[i]; i++; } } // 将token加入当前行 row.push_back(token); // 跳过逗号和空格 while (i<input.size() && (input[i]==',' || input[i]==' ')) i++; } else { i++; // 其他情况,跳过(比如空格等) } } return matrix; } int main() { string input; cout << "请输入格式为[[\"str1\",\"str2\"],[\"str3\"]]的字符串:" << endl; getline(cin, input); vector<vector<string>> matrix = parseStringMatrix(input); // 输出 cout << "解析后的二维字符串数组:" << endl; for (const auto& row : matrix) { for (const auto& s : row) { cout << s << " "; } cout << endl; } return 0; } ``` 注意:方案2的解析器是一个简化版本,它假设输入的格式是严格正确的。实际应用中可能需要更健壮的解析,比如处理转义字符、错误格式等。 另外,引用[1]中还提到了其他输入方式,比如输入单字符串、多个字符串等,但用户特别关注二维数组,所以我们重点在二维数组的输入输出。 总结:在C++中,处理字符串二维数组的输入输出,通常使用`vector<vector<string>>`来存储。输入方式可以根据实际需要选择逐行读取空格分隔的字符串,或者解析特定格式的字符串。 根据用户的问题,我提供了两种常见的方法。用户可以根据自己的输入格式选择合适的方法。 注意:在方案2中,我们假设字符串是用双引号括起来的。如果输入中没有双引号,而是用单引号或者没有引号,则需要调整解析逻辑。 最后,我们也可以使用现有的库(如JSON库)来解析复杂的格式,但这里为了简单,我们自行解析。 如果用户需要更详细的信息,可以参考引用[1]和引用[3]中的方法。 希望这些方法能够帮助用户解决问题。 </think>### C++字符串二维数组的输入输出方法 在C++中处理字符串二维数组的输入输出需要根据不同的数据格式灵活实现。以下是常见的三种实现方式,涵盖不同输入场景: #### 1. **逐行输入空格分隔的字符串(动态行数列数)** ```cpp #include <iostream> #include <vector> #include <string> #include <sstream> using namespace std; int main() { vector<vector<string>> str2D; string line; // 输入:每行多个空格分隔的字符串,空行结束 cout << "输入字符串矩阵(空格分隔,空行结束):\n"; while (getline(cin, line) && !line.empty()) { vector<string> row; stringstream ss(line); string word; while (ss >> word) { row.push_back(word); } str2D.push_back(row); } // 输出二维数组 cout << "输出结果:\n"; for (auto &row : str2D) { for (auto &s : row) { cout << s << " "; } cout << "\n"; } return 0; } ``` **输入示例**: ``` hello world cpp 2d array [空行] ``` **输出**: ``` hello world cpp 2d array ``` #### 2. **解析JSON格式的字符串矩阵(如`[["a","b"],["c"]]`)** ```cpp #include <iostream> #include <vector> #include <string> using namespace std; vector<vector<string>> parseStringMatrix(string input) { vector<vector<string>> matrix; vector<string> row; string token; bool inString = false; bool inVector = false; for (char c : input) { if (c == '[') { if (inVector) row.clear(); inVector = true; } else if (c == ']') { if (!token.empty()) { row.push_back(token); token = ""; } if (inVector && !row.empty()) { matrix.push_back(row); row.clear(); } inVector = false; } else if (c == '\"') { inString = !inString; if (!inString && !token.empty()) { row.push_back(token); token = ""; } } else if (inString) { token += c; } } return matrix; } int main() { string input = R"([["Hello","World"],["C++","2D","Array"]])"; auto matrix = parseStringMatrix(input); // 输出结果 for (auto &row : matrix) { for (auto &s : row) { cout << s << " "; } cout << "\n"; } return 0; } ``` **输出**: ``` Hello World C++ 2D Array ``` #### 3. **固定行数的逗号分隔输入** ```cpp #include <iostream> #include <vector> #include <string> #include <sstream> using namespace std; int main() { int rows; cout << "输入行数: "; cin >> rows; cin.ignore(); // 清除换行符 vector<vector<string>> str2D(rows); for (int i = 0; i < rows; ++i) { string line; cout << "输入第" << i+1 << "行(逗号分隔): "; getline(cin, line); stringstream ss(line); string cell; while (getline(ss, cell, ',')) { str2D[i].push_back(cell); } } // 输出 for (auto &row : str2D) { for (int j = 0; j < row.size(); ++j) { cout << row[j]; if (j < row.size()-1) cout << ", "; } cout << "\n"; } return 0; } ``` **输入示例**: ``` 3 apple,banana C++,Python,Java one,two,three ``` **输出**: ``` apple, banana C++, Python, Java one, two, three ``` ### 关键点说明 1. **输入处理**: - 使用`getline()`读取整行避免空格截断[^1] - 通过`stringstream`分割单词/逗号分隔值 - 引号处理需手动解析字符串边界 2. **数据结构**: - 推荐`vector<vector<string>>`动态存储 - 每行可以是不同长度的字符串数组[^4] 3. **特殊格式**: - JSON格式需处理`[ ] , " `等符号边界 - 引号内内容需完整保留(含空格)[^3] 4. **输出控制**: - 内层循环输出每行元素 - 分隔符保持与输入一致 > 提示:实际应用时需根据输入格式选择对应方案,对带空格的字符串建议用双引号包裹保证完整性[^1][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值