C++ Strings and Character Handling: ASCII, String Functions and Character Arrays

4
0

Strings and Character Handling

Text processing is an essential part of programming. Names, sentences, passwords, commands, and file contents are all represented using characters and strings. C++ provides several ways to work with textual data, ranging from individual char values and character arrays to the more flexible std::string class.

Working with Characters

The char data type stores a single character. A character literal is enclosed in single quotation marks, such as 'A', '7', or '%'.

A char typically occupies one byte of memory. The exact number of bits in a byte depends on the system, although modern systems commonly use eight-bit bytes.

#include <iostream>
using namespace std;
int main() {
char letter = 'A';
char digitCharacter = '7';
int number = 7;
cout << letter << '\n';
cout << digitCharacter << '\n';
cout << number << '\n';
cout << sizeof(letter) << '\n';
}

Although '7' and 7 look similar, they represent different values. The first is a character, while the second is an integer. Likewise, "Striver" is a string literal containing multiple characters and must be enclosed in double quotation marks.

A normal char should contain exactly one character. Expressions such as 'AB' are multi-character literals. They do not represent a regular two-character char; their value is implementation-defined and they should not be used to store textual data.

How Characters Are Stored

Computers internally store numeric values rather than visual symbols. Each character is associated with a numeric character code. When a character is printed, the corresponding symbol is displayed. When it is converted to an integer, its numeric code becomes visible.

ASCII Character Codes

ASCII stands for American Standard Code for Information Interchange. Standard ASCII assigns values from 0 to 127 to English letters, digits, punctuation marks, control characters, and whitespace characters.

  • The digits '0' to '9' have codes 48 to 57.

  • The uppercase letters 'A' to 'Z' have codes 65 to 90.

  • The lowercase letters 'a' to 'z' have codes 97 to 122.

These ranges are consecutive. This property makes it possible to classify and convert characters using arithmetic and comparisons.

#include <iostream>
using namespace std;
int main() {
char ch = 'A';
cout << ch << '\n';
cout << static_cast<int>(ch) << '\n';
cout << static_cast<int>('0') << '\n';
cout << static_cast<int>('9') << '\n';
cout << static_cast<int>('A') << '\n';
cout << static_cast<int>('Z') << '\n';
cout << static_cast<int>('a') << '\n';
cout << static_cast<int>('z') << '\n';
}

The entire ASCII table can also be explored by converting each integer from 0 to 127 into a character. Many early ASCII values are control characters, so they do not produce visible symbols. Code 32 represents a space, while code 127 represents the DEL control character.

#include <iostream>
using namespace std;
int main() {
for (int code = 0; code <= 127; code++) {
cout << code << ": "
<< static_cast<char>(code) << '\n';
}
}

Converting Between Digits and Characters

Because digit characters have consecutive codes, a digit character can be converted into its numeric value by subtracting '0'.

For example, the code of '7' is seven positions after the code of '0'. Therefore, '7' - '0' evaluates to the integer 7.

The reverse conversion is performed by adding '0' to an integer digit. These conversions apply to a single digit from 0 to 9, not to an arbitrary multi-digit number.

#include <iostream>
using namespace std;
int main() {
char digitCharacter = '7';
int digitValue = digitCharacter - '0';
int number = 5;
char convertedCharacter = static_cast<char>(number + '0');
cout << digitValue << '\n';
cout << convertedCharacter << '\n';
}

Escape Sequences

Some characters cannot be written conveniently as ordinary character literals. C++ represents them using escape sequences beginning with a backslash. Although an escape sequence may contain two typed symbols, it represents one character.

  • \n represents a newline.

  • \t represents a horizontal tab.

  • \\ represents a backslash.

  • \' represents a single quotation mark.

  • \" represents a double quotation mark inside a string.

  • \0 represents the null character.

#include <iostream>
using namespace std;
int main() {
cout << "First line\nSecond line\n";
cout << "Name\tScore\n";
cout << "Aman\t95\n";
cout << "C:\\Users\\Aman\n";
cout << "It\'s valid\n";
cout << "He said, \"Hello!\"\n";
}

Comparing and Classifying Characters

Characters can be compared because each character has an underlying numeric code. A manual uppercase check determines whether a character lies between 'A' and 'Z'. Similar checks can be written for lowercase letters and digits.

#include <iostream>
using namespace std;
int main() {
char ch;
cin >> ch;
if (ch >= 'A' && ch <= 'Z') {
cout << "Uppercase letter";
} else if (ch >= 'a' && ch <= 'z') {
cout << "Lowercase letter";
} else if (ch >= '0' && ch <= '9') {
cout << "Digit";
} else {
cout << "Other character";
}
}

Character Classification Functions

The <cctype> header provides standard character-classification functions:

  • isalpha(ch) checks whether the character is alphabetic.

  • isdigit(ch) checks whether it is a digit.

  • isalnum(ch) checks whether it is a letter or digit.

  • isspace(ch) checks for whitespace such as a space, tab, or newline.

  • islower(ch) checks for a lowercase letter.

  • isupper(ch) checks for an uppercase letter.

These functions return zero when the condition is false and a nonzero value when it is true. The true result is not guaranteed to be exactly 1, so it should be tested directly as a Boolean condition.

For ordinary ASCII examples, a character can be passed directly. When processing arbitrary char values, converting the argument to unsigned char first prevents undefined behavior for negative character values.

#include <cctype>
#include <iostream>
using namespace std;
int main() {
cout << isalpha('a') << '\n';
cout << isalpha('0') << '\n';
cout << isdigit('0') << '\n';
cout << isdigit('A') << '\n';
cout << isalnum('7') << '\n';
cout << isspace(' ') << '\n';
}

Reading Characters from Input

The extraction operator >> performs formatted input. When it reads a character, it skips leading whitespace by default. If the input contains several spaces followed by R, cin >> ch reads R.

The cin.get() function performs unformatted character input. It reads the next available character exactly as it appears, including spaces, tabs, and newline characters.

For input consisting of a space followed by R, the first cin.get() reads the space and the second reads R. For input containing R, a newline, and then A, three calls read all three characters in that order.

#include <iostream>
using namespace std;
int main() {
char first;
char second;
char third;
cin.get(first);
cin.get(second);
cin.get(third);
cout << "First: " << first << '\n';
cout << "Second: " << second << '\n';
cout << "Third: " << third << '\n';
}

C-Style Strings and Character Arrays

A C-style string is an array of characters terminated by the null character '\0'. This terminating character marks the end of the meaningful text.

The declaration char word[] = "hello"; creates an array containing six elements: the five visible characters and the terminating null character. Consequently, sizeof(word) produces 6.

#include <iostream>
using namespace std;
int main() {
char word[] = "hello";
cout << word << '\n';
cout << sizeof(word) << '\n';
cout << static_cast<int>(word[5]) << '\n';
}

Capacity and the Null Terminator

An array used as a C-style string must provide enough space for both its visible characters and the null terminator. The text "hello" therefore requires a capacity of at least six characters.

  • char word[4] = "hello"; is invalid because the array is too small.

  • char word[5] = "hello"; does not leave room for the required null terminator.

  • char word[6] = "hello"; provides the required capacity.

A character array is not automatically a valid C-style string. For example, an array explicitly initialized with the five characters 'h', 'e', 'l', 'l', and 'o' has no null terminator. Passing such an array to an operation that expects a C-style string can read beyond its boundary and cause undefined behavior.

If char word[8] = "hello"; is declared, the null terminator appears at index 5, immediately after the meaningful text. The remaining elements are also zero-initialized. The terminator does not need to occupy the final position of the array.

#include <iostream>
using namespace std;
int main() {
char word[8] = "hello";
cout << word << '\n';
cout << "Capacity: " << sizeof(word) << '\n';
word[6] = 'L';
word[7] = 'I';
cout << word << '\n';
cout << word[6] << word[7] << '\n';
}

The second printing of word still produces hello. The stream stops at the null character stored at index 5, so the characters placed after it are not considered part of the C-style string.

Finding the Length of a C-Style String

The length of a C-style string is the number of characters before the first null terminator. It does not include the terminator itself. The length can be calculated manually or obtained using strlen() from the <cstring> header.

#include <cstring>
#include <iostream>
using namespace std;
int main() {
char word[] = "hello";
int length = 0;
while (word[length] != '\0') {
length++;
}
cout << "Manual length: " << length << '\n';
cout << "strlen result: " << strlen(word) << '\n';
cout << "Array size: " << sizeof(word) << '\n';
}

Built-in character arrays have important restrictions. They do not support direct reassignment with =, cannot be extended using +=, and do not compare textual contents correctly with ==. Functions such as strcmp() are required for content comparison, while careful capacity management is required when modifying them.

Problem: Classify a Character

Given one character, classify it as uppercase, lowercase, digit, or another character. The checks should be arranged in an if-else if chain because a character belongs to only one of these categories.

  • 'G' is classified as uppercase.

  • 'h' is classified as lowercase.

  • '7' is classified as a digit.

  • A punctuation mark such as '#' is classified as other.

#include <cctype>
#include <iostream>
#include <string>
using namespace std;
string classifyCharacter(char ch) {
unsigned char value = static_cast<unsigned char>(ch);
if (isupper(value)) {
return "uppercase";
} else if (islower(value)) {
return "lowercase";
} else if (isdigit(value)) {
return "digit";
}
return "other";
}
int main() {
char ch;
cin >> ch;
cout << classifyCharacter(ch);
}

Problem: Convert a Digit Character into an Integer

Given a character that is guaranteed to be a digit, return its corresponding integer value. Subtracting '0' removes the character-code offset.

  • For '7', the function returns 7.

  • For '0', the function returns 0.

On a programming platform where the driver code handles input and output, only the conversion function may be required.

#include <iostream>
using namespace std;
int digitToNumber(char ch) {
return ch - '0';
}
int main() {
cout << digitToNumber('7') << '\n';
cout << digitToNumber('0') << '\n';
}

The C++ String Class

The std::string class, available through the <string> header, offers a safer and more convenient way to work with text. It manages its own storage, keeps track of its length, and can grow or shrink as text is modified.

Unlike a built-in character array, a string supports assignment, direct content comparison, concatenation, and numerous member functions.

#include <iostream>
#include <string>
using namespace std;
int main() {
string name = "Striver";
cout << name << '\n';
cout << name.size() << '\n';
name = "Aman";
cout << name << '\n';
string first = "hello";
string second = "hello";
cout << (first == second) << '\n';
}

Ways to Construct Strings

A string may be created as an empty string, initialized from a string literal, filled with repeated copies of a character, or copied from another string.

#include <iostream>
#include <string>
using namespace std;
int main() {
string emptyText;
string name = "Aman";
string repeated(5, 'x');
string copy = name;
cout << emptyText << '\n';
cout << name << '\n';
cout << repeated << '\n';
cout << copy << '\n';
}

String Size and Emptiness

The size() and length() member functions both return the number of characters in a string. They provide the same result and have equivalent performance. The empty() function determines whether the string contains zero characters.

  • text.size() returns the number of characters.

  • text.length() provides the same value.

  • text.empty() returns true when the string contains no characters.

Accessing String Characters

For a string of length n, valid indexes range from 0 to n - 1. An empty string has no valid character index.

The subscript operator [] does not perform bounds checking. Accessing an invalid index through it causes undefined behavior. The at() function checks the index and throws an out_of_range exception when it is invalid.

The front() and back() functions access the first and last characters. They must only be used when the string is not empty.

#include <iostream>
#include <string>
using namespace std;
int main() {
string text = "hello";
cout << text[1] << '\n';
cout << text.at(1) << '\n';
if (!text.empty()) {
cout << text.front() << '\n';
cout << text.back() << '\n';
}
}

Traversing and Modifying a String

An index-based loop provides both the character and its position. A range-based loop is useful when only the characters are required.

In a range-based loop, char ch creates a copy of each character. Modifying that copy does not alter the string. To update the original characters, use a reference such as char& ch.

#include <cctype>
#include <iostream>
#include <string>
using namespace std;
int main() {
string text = "hello";
for (int index = 0; index < text.size(); index++) {
cout << index << ": " << text[index] << '\n';
}
for (char ch : text) {
cout << ch << ' ';
}
cout << '\n';
for (char& ch : text) {
ch = static_cast<char>(toupper(static_cast<unsigned char>(ch)));
}
cout << text << '\n';
}

The final loop changes "hello" into "HELLO". The reference ensures that every assignment updates the corresponding character inside the original string.

Reading Words and Complete Lines

The extraction operator reads one whitespace-delimited word. If the input is hello world, one extraction stores only hello. Two extractions can read the two words separately.

The getline() function reads an entire line, including spaces, until it reaches a newline. The newline is removed from the input stream but is not stored in the resulting string.

#include <iostream>
#include <string>
using namespace std;
int main() {
string firstLine;
string secondLine;
getline(cin, firstLine);
getline(cin, secondLine);
cout << firstLine << '\n';
cout << secondLine << '\n';
}

For input containing the lines hello world and my name is Raj, the two calls store those complete lines separately.

When formatted input such as cin >> value is followed by getline(), the previous extraction may leave a newline in the input stream. That newline should be consumed before reading the next full line.

String Concatenation

Strings can be joined using the + operator. The += operator appends text to an existing string. The push_back() function appends one character, while pop_back() removes the final character.

The string must not be empty when pop_back() is called. The function modifies the string and does not return the removed character.

#include <iostream>
#include <string>
using namespace std;
int main() {
string first = "take";
string second = " you forward";
string combined = first + second;
cout << combined << '\n';
first += " you forward";
cout << first << '\n';
string text = "hello";
text.push_back('!');
cout << text << '\n';
text.pop_back();
cout << text << '\n';
}

Comparing Strings

C++ strings support direct content comparison. The == and != operators test equality and inequality. Relational operators such as < and > compare strings lexicographically.

Lexicographical comparison examines corresponding characters from left to right. The first differing pair determines the result according to the characters' numeric codes.

  • "Raj" comes before "Striver" because 'R' comes before 'S'.

  • When comparing "Raj" and "Rat", the first two characters match. The result is determined by 'j' and 't'.

  • Uppercase and lowercase letters have different character codes, so comparison is case-sensitive.

#include <iostream>
#include <string>
using namespace std;
int main() {
string first = "Raj";
string second = "Striver";
string third = "Rat";
cout << (first == second) << '\n';
cout << (first != second) << '\n';
cout << (first < second) << '\n';
cout << (first < third) << '\n';
}

Finding Text Inside a String

The find() function searches for a character or substring and returns the index of its first occurrence. If the requested text is absent, it returns string::npos.

The return value should be stored in string::size_type or size_t. Comparing the result with string::npos is more reliable than converting it to an integer and comparing it with -1.

#include <iostream>
#include <string>
using namespace std;
int main() {
string name = "Raj Striver";
string::size_type index = name.find("Striver");
cout << index << '\n';
string::size_type missing = name.find("TT");
if (missing == string::npos) {
cout << "Text not found\n";
}
}

In "Raj Striver", the substring "Striver" begins at index 4. Searching for "TT" produces string::npos.

Extracting a Substring

The substr(start, count) function creates a new string starting at the specified index and containing up to count characters. Omitting the count extracts everything from the starting index to the end.

  • name.substr(4, 7) produces "Striver".

  • name.substr(4, 6) produces "Strive".

  • name.substr(4, 4) produces "Stri".

  • name.substr(4) produces everything from index 4 onward.

  • name.substr(1) produces everything from the second character onward.

#include <iostream>
#include <string>
using namespace std;
int main() {
string name = "Raj Striver";
cout << name.substr(4, 7) << '\n';
cout << name.substr(4, 6) << '\n';
cout << name.substr(4, 4) << '\n';
cout << name.substr(4) << '\n';
cout << name.substr(1) << '\n';
}

Inserting, Erasing, and Replacing Text

Inserting Text

The insert() function places new text at a specified index. Inserting "'s name is" after "Raj" can transform "Raj Striver" into "Raj's name is Striver".

Erasing Text

The erase(start, count) function removes characters beginning at a selected index. Removing eight characters from index 3 in "Raj Striver" removes the space and the complete second word, leaving "Raj".

Erasing three characters from index 4 removes "Str", producing "Raj iver".

Replacing Text

The replace(start, count, replacement) function removes a specified section and inserts new text in its place.

  • Replacing seven characters from index 4 with "Vikram" produces "Raj Vikram".

  • Replacing only two characters from index 4 with "Vikram" replaces "St" but leaves "river", producing "Raj Vikramriver".

#include <iostream>
#include <string>
using namespace std;
int main() {
string inserted = "Raj Striver";
inserted.insert(3, "'s name is");
cout << inserted << '\n';
string erasedCompletely = "Raj Striver";
erasedCompletely.erase(3, 8);
cout << erasedCompletely << '\n';
string erasedPartially = "Raj Striver";
erasedPartially.erase(4, 3);
cout << erasedPartially << '\n';
string replacedCompletely = "Raj Striver";
replacedCompletely.replace(4, 7, "Vikram");
cout << replacedCompletely << '\n';
string replacedPartially = "Raj Striver";
replacedPartially.replace(4, 2, "Vikram");
cout << replacedPartially << '\n';
}

The starting position supplied to these functions must be valid. An invalid starting index can cause an out_of_range exception. If the requested count extends beyond the remaining text, the operation generally processes characters only up to the end of the string.

Passing Strings to Functions

A string can be passed to a function in three common ways.

  • Passing by value creates a copy. Changes affect only the local copy.

  • Passing by non-constant reference avoids copying and permits modification of the original string.

  • Passing by constant reference avoids copying while preventing modification. This is generally preferred for read-only access.

#include <iostream>
#include <string>
using namespace std;
void changeCopy(string text) {
text = "Changed copy";
}
void changeOriginal(string& text) {
text = "Changed original";
}
void display(const string& text) {
cout << text << '\n';
}
int main() {
string message = "Hello";
changeCopy(message);
display(message);
changeOriginal(message);
display(message);
}

Key Takeaways

  • A char stores one character, while a string stores a sequence of characters.

  • Characters are represented internally using numeric character codes.

  • ASCII letter and digit ranges enable direct character classification and conversion.

  • Formatted character input skips leading whitespace, while cin.get() reads whitespace exactly.

  • A C-style string is a null-terminated character array and requires manual capacity management.

  • A C++ string manages its storage and supports assignment, comparison, concatenation, searching, extraction, insertion, erasure, and replacement.

  • String indexes begin at zero, and valid access always remains within the current string length.

  • Use references when a function must modify the original string, and use constant references for efficient read-only access.

C++

Read Similar Blogs

Comments0