참조, 배열, 문자 배열, vector
참조, 배열, 문자 배열, vector
학습 흐름
참조 변수(별명) → 참조 초기화 규칙 → 참조 매개변수 (값 전달과 비교)
↓
1차원 배열(초기화, 크기, 함수 전달) → 배열 패턴(누적, 뒤집기, 이동)
↓
문자 배열과 '\0' → 2차원 배열 → 정렬과 탐색 → vector1. 참조 변수
int score = 80;
int& ref = score; // ref는 score의 별명
ref = 95; // score도 95참조 초기화 규칙 (시험 최빈출)
| 코드 | 판정 | 이유 |
|---|---|---|
int& r = a; | OK | 변수에 연결 |
int& r; | 컴파일 오류 | 참조는 선언할 때 반드시 대상을 지정 |
int& r = 5; | 컴파일 오류 | 일반 참조는 리터럴(임시 값)에 연결 불가 |
int& r = a + 1; | 컴파일 오류 | 식의 결과도 임시 값 |
const int& r = 5; | OK | const 참조는 임시 값에도 연결 가능 |
const int c = 3; int& r = c; | 컴파일 오류 | const 변수를 수정 가능한 참조로 연결 불가 |
const int& r = a; r = 10; | 컴파일 오류 | const 참조로는 수정 불가 |
int& r = arr[1]; | OK | 배열 원소에도 연결 가능 |
참조는 다시 연결되지 않는다
int a = 1, b = 2;
int& r = a;
r = b; // r을 b에 다시 연결하는 것이 아님! a에 b의 값(2)을 대입2. 참조 매개변수
| 선언 | 복사 | 원본 수정 | 넘길 수 있는 인수 |
|---|---|---|---|
void f(int x) | 함 | 불가 | 변수, 리터럴, 식 모두 |
void f(int& x) | 안 함 | 가능 | 수정 가능한 변수만 |
void f(const int& x) | 안 함 | 불가 | 변수, 리터럴, 식, const 변수 모두 |
void inc(int& n) { n++; }
int a = 1;
inc(a); // OK, a = 2
inc(5); // 컴파일 오류: 리터럴
inc(a + 1); // 컴파일 오류: 식의 결과
const int c = 3;
inc(c); // 컴파일 오류: const 변수참조 매개변수는 값을 바꾸는 swap이나 여러 결과를 돌려줄 때 씁니다.
void swapValues(int& a, int& b) { int t = a; a = b; b = t; }
void divide(int x, int y, int& q, int& r) { q = x / y; r = x % y; }다음 코드에서 컴파일 오류가 나는 줄을 모두 고르시오. 오류 줄을 지운 뒤의 출력을 쓰시오.
int a = 1, b = 2;
int& r1 = a; // (1)
int& r2; // (2)
int& r3 = 5; // (3)
const int& r4 = 5; // (4)
const int c = 3;
int& r5 = c; // (5)
const int& r6 = a; // (6)
r6 = 10; // (7)
r1 = b; // (8)
cout << a << ' ' << b << ' ' << r1 << ' ' << r4 << ' ' << r6;정답 확인
오류: (2), (3), (5), (7).
- (2) 참조는 초기화가 필수입니다.
- (3) 일반 참조는 리터럴에 연결할 수 없습니다. (4)처럼 const 참조는 됩니다.
- (5) const 변수를 수정 가능한 참조로 가리킬 수 없습니다.
- (7) const 참조로는 값을 바꿀 수 없습니다.
출력: 2 2 2 5 2 — (8)의 r1 = b는 a = 2와 같습니다. r6도 a를 가리키므로 2입니다.
다음 출력을 쓰시오.
void hi(int& x, int y) {
x = x * 2;
y = y * 2;
}
int main() {
int x = 3, y = 4;
int& rx = x;
hi(rx, y);
cout << x << ' ' << y << '\n';
int arr[3] = {1, 2, 3};
int& e = arr[1];
e = 9;
for (int v : arr) cout << v << ' ';
}그리고 x를 const int x = 3;으로 바꿀 수 있는지 설명하시오.
정답 확인
6 4
1 9 3x는 참조로 전달되어 6이 되고, y는 값으로 전달되어 함수 안의 복사본만 8이 됩니다. e는 arr[1]의 별명입니다.
const int x = 3;으로 바꿀 수 없습니다. int& rx = x;는 const 변수를 수정 가능한 참조로 연결하므로 오류이고, hi의 첫 매개변수도 int&라서 const 변수를 넘길 수 없습니다.
3. 1차원 배열
선언과 초기화
| 코드 | 결과 |
|---|---|
int a[5]; (함수 안) | 다섯 칸 모두 쓰레기 값 |
int a[5] = {}; 또는 {0} | 모두 0 |
int a[5] = {1, 2}; | {1, 2, 0, 0, 0} (나머지는 0) |
int a[] = {4, 5, 6}; | 크기 3으로 자동 결정 |
int a[3] = {1, 2, 3, 4}; | 컴파일 오류 (초기값이 너무 많음) |
int n = 5; int a[n]; | 표준 C++에서는 불가(크기는 상수여야 함) |
- 인덱스는 0부터 크기 − 1까지입니다.
a[5](크기 5)는 범위 초과 → UB입니다. 컴파일 오류가 아니며 실행 중 오류가 난다고 보장되지도 않습니다(족보에서는 "runtime error"로 분류하기도 함). - 배열 원소 개수 =
sizeof(a) / sizeof(a[0]).sizeof(a)는 바이트 수입니다(int5개면 20). - 배열끼리 대입할 수 없습니다.
a = b;는 컴파일 오류입니다. 원소를 하나씩 복사해야 합니다.
배열을 함수에 넘기면 원본이 바뀐다
배열 매개변수 int a[]는 배열 전체를 복사하지 않고 원본 배열을 가리킵니다. 그래서 함수 안에서 a[i]를 바꾸면 호출한 쪽 배열이 바뀝니다. 대신 함수는 배열 크기를 알 수 없으므로 크기를 따로 넘깁니다.
void mod(int a[], int n) { // 원본 배열을 바꿈
for (int i = 1; i < n; i++)
a[i] += a[i - 1];
}
void modv(int x) { x = 100; } // 원소 하나를 값으로 받으면 복사본
int a[] = {1, 2, 3, 4};
mod(a, 4);
modv(a[0]);
// a = {1, 3, 6, 10}다음 출력을 쓰시오.
int a[5] = {1, 2};
int b[] = {4, 5, 6};
int c[5] = {};
for (int v : a) cout << v << ' ';
cout << "| " << sizeof(a) / sizeof(a[0]) << ' '
<< sizeof(b) / sizeof(b[0]) << ' ' << sizeof(a) << ' ' << c[4] << '\n';그리고 다음 두 줄을 각각 판정하시오. int d[3] = {1, 2, 3, 4}; / int x[3], y[3]; x = y;
정답 확인
1 2 0 0 0 | 5 3 20 0int가 4바이트인 환경에서 sizeof(a)는 20입니다. 두 줄은 모두 컴파일 오류(초기값이 너무 많음 / 배열은 대입 불가)입니다.
자주 나오는 배열 패턴
// 최댓값 (첫 원소로 초기화!)
int mx = a[0];
for (int i = 1; i < n; i++) if (a[i] > mx) mx = a[i];
// 내적
int dot = 0;
for (int i = 0; i < n; i++) dot += a[i] * b[i];
// 뒤집기: 앞뒤를 바꾸며 가운데까지
for (int i = 0; i < n / 2; i++) { int t = a[i]; a[i] = a[n-1-i]; a[n-1-i] = t; }
// 왼쪽으로 한 칸 회전
int first = a[0];
for (int i = 0; i < n - 1; i++) a[i] = a[i + 1];
a[n - 1] = first;int a[] = {3, 8, 1, 9, 4};에 "왼쪽으로 한 칸 회전"을 실행한 결과를 쓰시오. 반복 조건을 i < n으로 잘못 쓰면 어떤 문제가 생기는가?
정답 확인
8 1 9 4 3. 조건을 i < n으로 쓰면 마지막 반복에서 a[i + 1]이 a[5]가 되어 **범위를 벗어난 읽기(UB)**가 됩니다.
4. 문자 배열과 C 문자열
C 문자열은 마지막에 널 문자 '\0'(코드 0)이 있는 char 배열입니다. 문자열 리터럴 "Hi"는 'H', 'i', '\0' 세 칸입니다.
char s[] = "Hi C++"; // 문자 6개 + '\0' = 7칸
char t[10] = "ab"; // 10칸: 'a' 'b' '\0' 그리고 나머지는 '\0'
char w[5] = "Hello"; // 컴파일 오류: '\0'까지 6칸 필요sizeof(s) | 문자 수(길이) | |
|---|---|---|
char s[] = "Hi C++"; | 7 | 6 |
char t[10] = "ab"; | 10 | 2 |
문자열 순회: '\0'을 만날 때까지
int len = 0;
while (s[len] != '\0') len++; // 직접 구한 길이
for (int i = 0; s[i] != '\0'; i++) { // 한 글자씩 처리
char c = s[i];
if (c >= 'a' && c <= 'z') s[i] = c - 'a' + 'A'; // 소문자 → 대문자
}알파벳 빈도 세기 (Homework 3 패턴)
int count[26] = {0}; // 반드시 0으로 초기화
for (int i = 0; s[i] != '\0'; i++) {
if (s[i] >= 'a' && s[i] <= 'z') count[s[i] - 'a']++;
else if (s[i] >= 'A' && s[i] <= 'Z') count[s[i] - 'A']++;
}다음 출력을 쓰시오.
// (1)
char s[] = "Hi C++";
int len = 0;
while (s[len] != '\0') len++;
char t[10] = "ab";
cout << sizeof(s) << ' ' << len << ' ' << sizeof(t) << '\n';
// (2) 대문자, 소문자, 숫자, 그 밖의 문자 개수
char u[] = "Hello World 2026!";
// (3)
char v[] = "banana";
int cnt[26] = {0};
for (int i = 0; v[i] != '\0'; i++) cnt[v[i] - 'a']++;
for (int i = 0; i < 26; i++)
if (cnt[i]) cout << char('a' + i) << cnt[i] << ' ';
// (4) 문자열 뒤집기
char w[] = "abcde";
int n = 0;
while (w[n] != '\0') n++;
for (int i = 0; i < n / 2; i++) { char c = w[i]; w[i] = w[n-1-i]; w[n-1-i] = c; }
cout << w;정답 확인
(1) 7 6 10
(2) 대문자 2(H, W), 소문자 8, 숫자 4, 그 밖의 문자 3(공백 2개, !).
(3) a3 b1 n2
(4) edcba — 마지막 인덱스는 n - 1입니다. '\0'(인덱스 n)과 바꾸면 문자열이 빈 문자열이 됩니다.
5. 2차원 배열
2차원 배열은 행 × 열 표입니다. a[행][열]로 접근합니다.
int m[2][3] = {
{1, 2, 3}, // 행 0
{4, 5, 6} // 행 1
};
cout << m[1][0]; // 4 (행 1, 열 0)초기화 규칙
| 코드 | 결과 |
|---|---|
int m[2][3] = {1, 2, 3, 4}; | 행 순서로 채움: {1, 2, 3}, {4, 0, 0} |
int m[2][3] = {{1}, {4, 5}}; | 행마다 채움: {1, 0, 0}, {4, 5, 0} |
int m[][3] = {{1}, {2, 3}, {4, 5, 6}}; | 행 수만 생략 가능 → 3행 |
int m[2][] = {…}; | 컴파일 오류: 열 수는 생략 불가 |
메모리는 행 우선(row-major)
m[2][3]은 메모리에 m[0][0] m[0][1] m[0][2] m[1][0] m[1][1] m[1][2] 순서로 한 줄로 저장됩니다. 그래서 중괄호 없이 {1, 2, 3, 4}를 쓰면 0행부터 채워집니다.
- 행 수 =
sizeof(m) / sizeof(m[0]) - 열 수 =
sizeof(m[0]) / sizeof(m[0][0])
순회 패턴
const int R = 3, C = 4;
int m[R][C] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
// 행 합: 바깥 = 행, 안쪽 = 열
for (int i = 0; i < R; i++) {
int sum = 0;
for (int j = 0; j < C; j++) sum += m[i][j];
cout << sum << ' '; // 10 26 42
}
// 열 합: 바깥 = 열, 안쪽 = 행
for (int j = 0; j < C; j++) {
int sum = 0;
for (int i = 0; i < R; i++) sum += m[i][j];
cout << sum << ' '; // 15 18 21 24
}
// 주대각선 (정사각 부분): m[i][i]
// 반대 대각선 (N×N): m[i][N-1-i]
// 아래 삼각형 (대각선 포함): i >= j
// 전치(행과 열 바꿔 출력): 바깥 j, 안쪽 i로 m[i][j]함수에 넘기기: 열 크기는 반드시 적는다
const int COLS = 3;
void printM(int m[][COLS], int rows) { // 첫 번째 [] 만 비울 수 있음
for (int i = 0; i < rows; i++) {
for (int j = 0; j < COLS; j++) cout << m[i][j] << ' ';
cout << '\n';
}
}다음 출력을 쓰시오.
// (1)
int m[2][3] = {1, 2, 3, 4};
int b[2][3] = {{1}, {4, 5}};
int c[][3] = {{1}, {2, 3}, {4, 5, 6}};
printM(m, 2);
printM(b, 2);
cout << sizeof(c) / sizeof(c[0]) << ' ' << sizeof(c[0]) / sizeof(c[0][0]) << '\n';
// (2)
int a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int s = 0, t = 0;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
if (i >= j) s += a[i][j];
for (int i = 0; i < 3; i++) t += a[i][2 - i];
cout << s << ' ' << t << '\n';정답 확인
(1)
1 2 3
4 0 0
1 0 0
4 5 0
3 3(2) 34 15 — i >= j는 대각선 포함 아래 삼각형(1 + 4 + 5 + 7 + 8 + 9 = 34), a[i][2 - i]는 반대 대각선(3 + 5 + 7 = 15).
int m[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};일 때
- 행 합, 열 합,
m[i][i](i = 0~2)의 합을 쓰시오. - 행과 열을 바꿔(전치하여) 출력하는 코드를 쓰고 출력 결과를 쓰시오.
- 다음 코드의
cnt를 쓰시오.
int k[2][3] = {{1, 2, 3}, {4, 5, 6}};
int cnt = 0;
for (int i = 0; i < 2; i++)
for (int j = 0; j < 3; j++) {
if (j == 1) continue;
if (k[i][j] > 4) break;
cnt += k[i][j];
}정답 확인
1. 행 합 10 26 42, 열 합 15 18 21 24, 대각선 합 1 + 6 + 11 = 18.
2.
for (int j = 0; j < 4; j++) { // 원래의 열이 새 행
for (int i = 0; i < 3; i++) cout << m[i][j] << ' ';
cout << '\n';
}1 5 9
2 6 10
3 7 11
4 8 123. 8 — 행 0: 1 더함, j = 1 건너뜀, 3 더함(4). 행 1: 4 더함(8), j = 1 건너뜀, 6 > 4라서 break(안쪽 반복만 종료).
2×3 행렬 A와 3×2 행렬 B의 곱 C(2×2)를 구하는 3중 반복문을 쓰고 C를 구하시오.
int A[2][3] = {{1, 2, 3}, {4, 5, 6}};
int B[3][2] = {{1, 0}, {0, 1}, {1, 1}};
int C[2][2] = {};정답 확인
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
for (int k = 0; k < 3; k++)
C[i][j] += A[i][k] * B[k][j];C = {{4, 5}, {10, 11}}. 예: C[0][0] = 1·1 + 2·0 + 3·1 = 4. C를 {}로 0 초기화하지 않으면 쓰레기 값에 더해집니다.
6. 정렬과 탐색
버블 정렬: 이웃끼리 비교해 큰 값을 뒤로
한 패스가 끝날 때마다 가장 큰 값이 맨 뒤로 확정됩니다. 교환이 한 번도 없으면 이미 정렬된 것입니다.
for (int pass = 1; pass < n; pass++) {
bool swapped = false;
for (int j = 0; j < n - pass; j++)
if (a[j] > a[j + 1]) { int t = a[j]; a[j] = a[j + 1]; a[j + 1] = t; swapped = true; }
if (!swapped) break;
}선택 정렬: 남은 것 중 최솟값을 앞으로
i번째 패스에서 a[i..n-1]의 최솟값을 찾아 a[i]와 한 번만 교환합니다.
for (int i = 0; i < n - 1; i++) {
int mi = i;
for (int j = i + 1; j < n; j++)
if (a[j] < a[mi]) mi = j;
int t = a[i]; a[i] = a[mi]; a[mi] = t;
}이진 탐색: 정렬된 배열에서 절반씩
int lo = 0, hi = n - 1;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (a[mid] == key) return mid;
if (a[mid] < key) lo = mid + 1;
else hi = mid - 1;
}
return -1;{5, 1, 4, 2, 8}을 위 버블 정렬로 정렬할 때 패스마다의 배열을 쓰시오. 몇 번째 패스에서 끝나는가?{64, 25, 12, 22, 11}을 선택 정렬할 때 i마다 교환 후의 배열을 쓰시오.{2, 5, 8, 12, 16, 23, 38, 56, 72, 91}에서 23을 이진 탐색할 때[lo, hi]와mid를 차례로 쓰시오.
정답 확인
1.
pass 1: 1 4 2 5 8
pass 2: 1 2 4 5 8
pass 3: 1 2 4 5 8 ← 교환이 없어 여기서 종료2.
i=0: 11 25 12 22 64
i=1: 11 12 25 22 64
i=2: 11 12 22 25 64
i=3: 11 12 22 25 643. [0, 9] mid=4(16 < 23) → [5, 9] mid=7(56 > 23) → [5, 6] mid=5(23 발견). 비교 3번, 인덱스 5.
퀵 정렬과 힙 정렬은 Exercise 05에서 단계별로 추적합니다.
7. vector
#include <vector>
vector<int> v = {90, 85};
v.push_back(100); // 끝에 추가
v.size(); // 3
v.pop_back(); // 끝 원소 제거
v[0]; // 범위 검사 없음
v.at(0); // 범위를 벗어나면 예외(out_of_range)
vector<int> z(5); // 0으로 채운 5칸다음 출력을 쓰시오.
void addV(vector<int> v) { v.push_back(9); v[0] = 0; }
void addR(vector<int>& v) { v.push_back(9); v[0] = 0; }
vector<int> v = {1, 2, 3};
addV(v);
cout << v.size() << ' ' << v[0] << ' ';
addR(v);
cout << v.size() << ' ' << v[0] << '\n';
int a[] = {1, 2, 3};
for (int x : a) x *= 10;
cout << a[0];
for (int& x : a) x *= 10;
cout << ' ' << a[0];정답 확인
3 1 4 0
1 10addV는 복사본을 바꾸므로 원본은 그대로이고, addR은 원본에 9를 추가하고 첫 원소를 0으로 바꿉니다. 범위 기반 for도 int x는 복사본, int& x는 원본입니다.
핵심 정리
- 참조는 선언과 동시에 연결해야 하고, 다시 연결되지 않는다.
r = b는 대입이다. - 일반 참조(
int&)는 리터럴·식·const 변수에 연결할 수 없다.const int&는 모두 가능하다. int&매개변수는 원본을 바꾸고, 값 매개변수는 복사본만 바꾼다.- 배열: 부분 초기화하면 나머지는 0, 초기화하지 않은 지역 배열은 쓰레기 값, 초기값이 크기보다 많으면 컴파일 오류, 범위 초과는 UB.
- 원소 개수 =
sizeof(a) / sizeof(a[0])(함수 매개변수에서는 불가). 배열끼리 대입 불가. - 배열을 함수에 넘기면 원본이 바뀐다. vector는 값으로 넘기면 복사된다.
- C 문자열은
'\0'으로 끝난다. 배열 크기는 문자 수 + 1. 빈도는count[c - 'a']++. - 2차원 배열은 행 우선으로 저장되고, 함수 매개변수에서 열 크기는 필수다.
a[i][j]의 i가 행, j가 열. - 버블 정렬은 패스마다 최댓값이 뒤로, 선택 정렬은 최솟값을 앞으로 보낸다.
내용을 이해했다면 학습 완료를 체크해 주세요.