Hello World
#include <iostream>
#include "windows.h"
using namespace std;
int main(void){
int a;
int b;
cout<<"Please input a and b:\n";
cin>>a;
cin>>b;
cout<<"a+b=";
cout<<a+b;
system("pause");
return 0;
}
在 VS Code 中运行 C++ 代码会出现控制台窗口闪退的问题
在代码中加入
#include "windows.h"
和system("pause");
可以解决
iostream 与 iostream.h
#include <iostream.h>
是非标准输入输出流,标准化前的头文件
#include <iostream>
是标准输入输出流,标准化后的头文件,需要配合命名空间使用 using namespace std;
基本数据和表达式
关键字
auto break case char class const continue default delete else
enum explicit extern float for friend goto if inline int long
new operator private protected public register return short signed
sizeof static struct switch this typedef union unsigned virtual
void while
标识符
以字母、下划线开始,由字母、下划线、数字组成的字符串
- 不能使用关键字
- 区分大小写
- 长度无规定
合法:
a x1 no_1 _a2c sum Name name
非法:
2a x+y a,b a&b const
数据类型
数据类型长度
printf("%d\n",sizeof(byte));
printf("%d\n",sizeof(short));
printf("%d\n",sizeof(int));
printf("%d\n",sizeof(long));
printf("%d\n",sizeof(float));
printf("%d\n",sizeof(double));
printf("%d\n",sizeof(boolean));
printf("%d\n",sizeof(char));
printf("\n");
printf("%d\n",sizeof(long int));
printf("%d\n",sizeof(long long));
1
2
4
4
4
8
1
1
4
8
byte | short | int | long | float | double | boolean | char |
---|---|---|---|---|---|---|---|
1 | 2 | 4 | 4 | 4 | 8 | 1 | 1 |
long int | long long |
---|---|
4 | 8 |
int
十进制(Decimal, base 10)
八进制(Octal, Base 8)
十六进制(Hexadecimal, base 16)
bool
只有两个值:true
和 false
enum
枚举类型,用标识符代替在列表中的序号
#include <iostream>
#include "windows.h"
using namespace std;
enum color{red, blue, green};
int main(void){
color c;
c = green;
cout << c;
system("pause");
return 0;
}
2
red = 0
blue = 1
green = 2
浮点型(float, double, long double)
常用示数方式
科学示数方式
char
转义字符
数据对象和访问
程序使用内存单元存放数据,且可以对内存单元进行命名(标识符)
对内存的读、写称为访问
既能读,又能写的内存对象称为变量
一旦初始化就不能修改的内存对象称为常量
变量
变量是存储单元
变量定义:申请指定类型的存储空间,并以指定标识符命名
访问变量
内存单元由操作系统按字节(Byte)编号,称为地址
一个对象占有内存的第一个字节的地址称为对象的地址
可以通过对象名或地址访问对象
数据对象有两种访问形式:读、写
地址访问
程序编译后,系统对已声明对象生成一张名表,登记对象的属性
<名字,类型,地址>
C++ 允许通过名或地址访问对象
间址运算符
以下两种方式是等效的:a == *(&a)
指针变量
能够存放对象地址的变量
&
取地址*
间址访问
下图,a == *p1
,b == *p2
,两者等效
区别 int *p 和 *p
前者是指针定义,后者是指针所指向的变量
区别 int 和 int *
若编译以下代码则会报错:
int i = 1;
int *p;
p = i;
不能将 "int" 类型的值分配到 "int *" 类型的实体
即类型不匹配,int
和 int *
不属于同种类型
输出 p 和 *p
int i = 1;
int *p = &i;
cout << p; //输出存放的地址
0x61ff08
int i = 1;
int *p = &i;
cout << *p; //输出指向变量的值
1
交换指针存储的地址
int a = 1;
int b = 2;
int *p1 = &a;
int *p2 = &b;
int *p;
p = p1;
p1 = p2;
p2 = p;
cout << *p1;
2
交换指针所指变量的值
int a = 1;
int b = 2;
int *p1 = &a;
int *p2 = &b;
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
cout << a;
2
空指针
以下两种方式均可以创建空指针:
int *p = 0;
int *p = null;
指向指针的指针
int **pp;
int *p;
int i = 1;
p = &i;
pp = &p;
cout << p << endl;
cout << *p << endl;
cout << pp << endl;
cout << *pp << endl;
cout << **pp << endl;
0x61ff04 // 指针 p 存放的地址,即 i 的地址
1 // *p
0x61ff08 // 指针 pp 存放的地址,即指针 p 自身的地址
0x61ff04 // 指针 *pp 存放的地址,即指针 p 存放的地址,即 i 的地址
1 // **pp
指针类型转换
int *intPointer;
char *charPointer;
int i = 65;
intPointer = &i;
charPointer = (char *)intPointer;
cout << *charPointer << endl;
A
ascii 码表中,十进制 65
对应的字符为 A
引用
int i;
int *p;
int &iRefer = i;
i = 1;
p = &i;
cout << i << endl;
cout << *p << endl;
cout << iRefer << endl;
1
1
1
iRefer
相当于 i
的别名,且必须在定义时初始化
i == *p == iRefer
,三者等效
注意区别 int &
和 &i
,前者是引用,后者是取地址符
常量
关键字 const
可以约束访问对象的权限,使其只能读,不能写,不允许修改对象的值
const double PI = 3.14;
const int MAX = 666;
常量指针
const 在星号之前
const 类型 * 指针名
类型 const * 指针名
常量指针,既可以指向常量,也可以指向变量,但是若指向变量,则不能修改指向的变量值
const int i = 114514;
int j = 1919;
const int *p = &i;
p = &j;
//报错,不能通过常量指针修改变量值
//*p = 666;
cout << *p << endl;
1919
指针常量
const 在星号之后,注意与常量指针区分
类型 * const 指针名
指针常量,其中保存的地址不可改变,即不可再指向别的变量
int i = 114514;
int j = 1919;
int * const p = &i;
//报错,不可修改指针保存的地址
//p = &j;
cout << *p << endl;
指向常量的指针常量
星号两边都有 const
const 类型 * const 指针名
指向常量的指针常量:
- 指针保存的地址不可修改
- 指针指向的常量值不可修改
const int i = 114514;
const int * const p = &i;
cout << *p << endl;
常引用
const 类型 & 引用名
int i = 114514;
const int & refer = i;
//报错,不能通过常引用修改变量值
//refer = 1919;
cout << refer << endl;
表达式
由数据和运算符,按一定规则,表达某个值的式子
运算符
运算符可分为:
单目运算符:
运算符 右操作数
-123
+500
双目运算符:
左操作数 运算符 右操作数
a + 1
x > y
三目运算符:
操作数1 ? 操作数2 : 操作数3
a ? b : c
运算符优先级
运算符多义性
int a = 1;
// 引用说明符
int &ra = a;
// 指针说明符 取址运算符
int *p = &a;
// 算术乘法
a = ra * 6;
// 间址访问 算术乘法
*p = 5 * *p; // 等同于 *p = 5 * (*p)
cout << *p << endl;
30
C++ 取余运算 %
c++ 中 %
运算符为取余,而不是取模
a % b
:
- 计算整数商:
c = a / b
(向 0 方向取整,如 1.5 取 1,-1.5 取 -1) - 计算余数:
r = a - c * b
数据输入和输出
c++ 的输入输出操作由 IO 流库提供
cin
、cout
是 IO 流库定义的两个标准对象,在 iostream.h
头文件中声明
输入
cin >> 变量1 >> 变量2 >> 变量3 >> ...
这里的 >>
是提取运算符,不是位运算符
输出
cout << 表达式1 << 表达式2 << 表达式3 << ...
这里的 <<
是插入运算符,不是位运算符
输出格式控制符
程序控制结构
选择控制
if 语句
switch 语句
switch(表达式){
case 常量表达式:
...
break;
case 常量表达式:
...
break;
default:
...
}
- 表达式必须为整型、枚举类型,不能为浮点型
- 常量表达式必须是常量,且与 switch 括号中的变量类型一致
- default 可选
循环控制
while 语句
do while 语句
for 语句
for(表达式1;表达式2;表达式3){
...
}
表达式 1、表达式 2、表达式 3 均可省略
转向控制
break 语句
continue 语句
goto 语句
函数
- 函数(Function)是功能抽象模块
- 函数的作用:任务划分、代码重用
函数定义
由两部分组成:函数头、函数体
返回值类型 函数名( 参数列表 ){
...
}
如
int getMax(int x, int y){
if(x > y){
return x;
}else {
return y;
}
}
函数参数传递
值传递
把传入参数(实际参数)的值复制给函数的形式参数,在这种情况下,修改函数内部的形参不会改变实参
// 函数定义
void swap(int x, int y)
{
int temp;
temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
// 调用函数来交换值
swap(a, b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 100
交换后,b 的值: 200
指针传递
把传入参数的地址复制给形式参数,修改形参会影响实参
// 函数定义
void swap(int *x, int *y)
{
int temp;
temp = *x; /* 保存地址 x 的值 */
*x = *y; /* 把 y 赋值给 x */
*y = temp; /* 把 x 赋值给 y */
return;
}
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值
* &a 表示指向 a 的指针,即变量 a 的地址
* &b 表示指向 b 的指针,即变量 b 的地址
*/
swap(&a, &b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100
引用传递
把传入参数的引用的地址复制给形参,在这种情况下,形参就是实参的别名,修改形参会影响实参
// 函数定义
void swap(int &x, int &y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */
return;
}
int main ()
{
// 局部变量声明
int a = 100;
int b = 200;
cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;
/* 调用函数来交换值 */
swap(a, b);
cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;
return 0;
}
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100
递归调用
- 递归形式(算法)
- 修改条件(缩小问题规模)
- 终止条件
// 求阶乘 n!
int F(int n){
if(n == 0){ // 终止条件
return 1;
}else {
return n * F(n - 1); // 修改条件 n = n - 1;
}
}
// 求斐波那契数列第 n 项
int F(int n){
if(n <= 2){
return 1;
}else {
return F(n - 1) + F(n - 2);
}
}
函数指针
- 每个函数都有一个首地址,称为函数的入口地址(函数指针)
- 不带括号的函数名就是函数入口地址
- 函数调用:找到函数入口地址、传递参数
函数的地址
void hello(){
cout << "hello" << endl;
}
int main(void){
// 以下三种方式等效,都能调用 hello 函数
hello();
(& hello)();
(*& hello)();
cout << hello << endl; // 函数名是地址
cout << (& hello) << endl; // 取函数地址
cout << (*& hello) << endl; // 函数地址所指对象
system("pause");
return 0;
}
注意,& hello
和 *& hello
都会被解析为函数
函数的指针
- 指向函数的指针称为函数指针
- 函数的类型是函数的接口
- 可以通过指针变量的间址方式调用函数
函数类型
// 以下为相同类型的函数
int max(int x, int y);
int min(int x, int y);
int avg(int x, int y);
它们的类型为 int (int, int)
定义函数类型
typedef 返回值类型 函数类型名 (参数列表)
如
typedef int myFunctionType(int, int);
定义指针变量
int (*fp)(int, int);
//或
myFunctionType *fp;
#include <iostream>
#include "windows.h"
using namespace std;
int max(int x, int y);
int min(int x, int y);
int avg(int x, int y);
int main(void){
int (*fp) (int, int);
fp = max;
cout << fp(1,3) << endl;
fp = min;
cout << fp(1,3) << endl;
fp = avg;
cout << fp(1,3) << endl;
system("pause");
return 0;
}
int max(int x, int y){
...
}
int min(int x, int y){
...
}
int avg(int x, int y){
...
}
#include <iostream>
#include "windows.h"
using namespace std;
int max(int x, int y);
int min(int x, int y);
int avg(int x, int y);
typedef int myIntType(int, int);
int main(void){
myIntType *fp1, *fp2, *fp3;
fp1 = max;
fp2 = min;
fp3 = avg;
cout << fp1(1,3) << endl;
cout << fp2(1,3) << endl;
cout << fp3(1,3) << endl;
system("pause");
return 0;
}
int max(int x, int y){
...
}
int min(int x, int y){
...
}
int avg(int x, int y){
...
}
注意,对于第一个程序 fp = max
,fp
存放的是函数的地址,max
是函数的直接地址
所以 fp
就是 max
(&max
),而 &fp
不等于 max
(&max
)
内联函数
内联函数是 C++ 为降低小规模函数的调用开销的一种机制
这种机制是编译器在编译时,将内联函数的调用以相应代码代替
内联函数声明:inline 函数名()
inline int hello();
int main(void){
...
}
int hello(){
...
}
inline int hello(){
...
}
int main(void){
...
}
错误示例:
//重复说明,语法错误
inline int hello();
int main(void){
...
}
inline int hello(){
...
}
//视为普通函数,inline 关键字无效
int hello();
int main(void){
}
inline int hello(){
...
}
函数重载
多个函数:
- 参数个数相同,但类型不同、返回值类型不同
- 参数个数不同、返回值类型不同
上述两种情况均可实现函数重载
另外注意:
仅返回值类型不同,属于语法错误,编译器无法确定唯一函数(函数重定义)
程序内存区域
自动存储类
静态存储类
- 关键字
extern
和static
声明静态存储变量和函数 extern
声明全局,static
声明局部- 两者说明变量时,程序开始执行时,就会分配和初始化内存空间,静态变量默认初始值为 0
- 两者说明函数时,程序开始执行时,就存在这个函数
标识符作用域
函数原型作用域
函数原型形式参数列表中的标识符具有函数原型作用域
int fun(int, int);
int fun(int a, int b);
int fun(int x, int y);
三种函数原型,编译器视为相同
块作用域
在语句块中声明的标识符具有块作用域
int main(void){
int a = 111;
{
int a = 222;// 内层的 a 覆盖了外层的 a
cout << a << endl;
}
cout << a << endl;
return 0;
}
222
111
函数作用域
语句标号(后面带冒号的标识符)是唯一具有函数作用域的标识符
如 switch
的 case
标号
文件作用域
任何在函数之外声明的标识符具有文件作用域
这种标识符从声明处到文件结尾,其中任何函数都可见
全局变量和局部变量
- 全局变量:文件作用域
- 局部变量:函数作用域、块作用域
全局变量声明时默认初始值为 0
当局部变量和全局变量同名时,局部变量在块内会覆盖全局变量
若要在块内访问全局变量,则需要域运算符 ::
int x;
int main(void){
int x = 666;
cout << x << endl;
cout << ::x << endl;
system("pause");
return 0;
}
666
0
预处理指令
C++ 编译器工作过程
- 预处理器:改善程序的组织管理
- 预处理指令:所有编译指令以
#
开头,每条指令单独占一行
文件包含
include
指令在编译之前,把指定文件包含到该命令所在位置
#include <文件名>
#include "文件名"
条件编译
#if 常量表达式
程序段
#endif
#if 常量表达式
程序段1
#else
程序段2
#endif
#if 常量表达式1
程序段1
#elif 常量表达式2
程序段2
...
#elif 常量表达式n
程序段n
#else
程序段n+1
#endif
宏定义
用指定正文替换程序中出现的标识符
#define 标识符 文本
分号可加可不加
不带参宏定义
#define PI 3.14
带参宏定义
#define getArea(r) PI * r * r
命名空间
- 命名空间是类、函数、对象、类型和其他名字的集合
- 命名空间可以让程序组件之间不会产生命名冲突
std
是 C++ 标准名空间
标准名空间
C++ 标准头文件没有扩展名 .h
如
iostream iomanip limit fstream string typeinfo stdexcept
使用标准类库的组件时,需要指定名空间
C++ 标准名空间 std
使用标准名空间
#include<iostream>
using namespace std;
int main(){
int a, b;
cin >> a;
cin >> b;
cout << a+b << endl;
}
#include<iostream>
using std::cin;
using std::cout;
int main(){
int a, b;
cin >> a;
cin >> b;
cout << a+b << endl;
}
#include<iostream>
int main(){
int a, b;
std::cin >> a;
std::cin >> b;
std::cout << a+b << std::endl;
}
数组
- 数组是由一定数量的同类元素,按顺序排列而成的数据结构
- 一个数组在内存中占有一块连续的内存区域
- 数组名就是数组首元素的地址
- 数组每个元素由下标进行表示
一维数组
元素是基本类型、结构类型或类
一维数组也可视为向量
定义
类型 标识符[表达式]
int A[10];
char B[MaxSize];
double C[m*n];
初始化
// 为数组赋初值
int a[5] = {1,2,3,4,5};
// 全部初始化为0
int b[10] = {0};
// 第0、1、2号元素赋初值,其他为0
int c[10] = {1,2,3};
// 自动推断数组长度为5
int d[] = {1,2,3,4,5}
下标访问
数组名 [表达式]
指针访问
int a[5] = {1,2,3,4,5};
// 数组名是首元素地址
a == &a[0]
a+1 == &a[1]
// 指针访问
*a == a[0]
*(a+1) == a[1]
指针数组
元素类型为指针的数组
每个指针元素都存放了某个地址
类型 * 标识符[表达式]
指向基本元素的指针数组
int a = 1;
int b = 2;
int c = 3;
int * pArr[3];
pArr[0] = &a;
pArr[1] = &b;
pArr[2] = &c;
cout << *pArr[0] << endl;
cout << *pArr[1] << endl;
cout << *pArr[2] << endl;
1
2
3
指向数组的指针数组
int a[2] = {1,2};
int b[2] = {3,4};
int c[2] = {5,6};
int (*pArr[3])[2];
pArr[0] = &a;
pArr[1] = &b;
pArr[2] = &c;
for(int i = 0; i < 3; i++){
for(int j = 0; j < 2; j++){
cout << *(*pArr[i] + j) << endl;
}
}
关于本例中 a
和 &a
:
a
作为一个数组名,本身就是首元素地址
&a
没有实际意义,与 a
等效,首元素地址
但是注意:
pArr[]
数组元素(指针)指向一维数组,其元素类型为二级指针
而 a
是一级指针,若写成 pArr[0] = a
则会报错
所以需要写成pArr[0] = &a
,两边逻辑上都是二级指针
指向函数的指针数组
#include <iostream>
#include "windows.h"
using namespace std;
const double PI = 3.14;
double getArea(double r){
return PI * r * r;
}
double getGirth(double r){
return 2 * PI * r;
}
// 定义函数类型
typedef double circleFunction(double);
int main(void){
// 利用函数类型,定义函数指针
circleFunction * pArr[2];
pArr[0] = getArea;
pArr[1] = getGirth;
cout << (*pArr[0])(3.0) << endl;
cout << (*pArr[1])(3.0) << endl;
system("pause");
return 0;
}
28.26
18.84
二维数组
元素类型为一维数组的数组
其中的每个一维数组元素类型相同、长度相同
下标访问
数组名[表达式1][表达式2]
指针访问
int a[3][5];
二维数组名 a
是逻辑上的二级指针,a[i]
是一级指针
但是,二维数组名不能直接赋给二级指针变量,因为无论 n 维数组,数组名都是首元素地址
int *p1;
int **p2;
// 正确
p1 = *a;
p1 = a[0];
// 错误
p1 = a;
p2 = a;
求元素地址
// 第0行第1列的元素地址
a[0] + 1
*a + 1
&a[0][1]
// 第3行第4列的元素地址
a[3] + 4
*(a + 3) + 4
&a[3][4]
// 第i行第j列的元素地址
a[i] + j
*(a + i) + j
&a[i][j]
求元素的值
// 第1行第2列元素的值
*(a[1] + 2)
*(*(a + 1) + 2)
a[1][2]
// 第i行第j列元素的值
*(a[i] + j)
*(*(a + i) + j)
a[i][j]
应用
int a[3][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
int *p;
for(p = a[0]; p < a[0] + 15; p++){
cout << *p << endl;
}
for(p = *a; p < *a + 15; p++){
cout << *p << endl;
}
for(int i = 0; i < 3; i++){
for(int j = 0; j < 5; j++){
cout << *(a[i] + j) << endl;
}
}
可以改为
p = a
吗?NO.
a
是逻辑上的二级指针,不能赋给一级指针变量p
可以改为
p = a[0][0]
吗?NO.
a[0][0]
是元素值可以改为
p = &a[0][0]
吗?YES.
一维数组作为函数参数
- 数组作为参数,与一般变量相同
- 数组名作为参数时,传递首元素地址
int sumArr(int arr[], int len){
int sum = 0;
// arr 本质是一个指针
for(int i = 0; i < len; i++){
sum += *arr;
arr++;
}
return sum;
}
int main(void){
int a[3] = {1, 2, 3};
cout << sumArr(a, 3) << endl;
system("pause");
return 0;
}
6
形式参数
int arr[]
本质上就是一个普通指针,等价于int * arr
也可以换成:
for(int i = 0; i < len; i++){
sum += arr[i];
}
二维数组作为函数参数
以下是常见错误:
#include <stdio.h>
void foo(int a[][], int m, int n) {
int i = 1;
int j = 1;
printf("a[%d][%d]=%d\n", i, j, a[i][j]);
}
int main() {
int a[2][3] = {
{1,2,3},
{4,5,6}
};
foo(a, 2, 3);
}
报错:
$ gcc test.c
test.c:3:14: error: array type has incomplete element type ‘int[]’
void foo(int p[][], int m, int n) {
^
test.c: In function ‘main’:
test.c:12:9: error: type of formal parameter 1 is incomplete
foo(a, 2, 3);
报错原因:
C 语言对二维数组的存储是按一维数组来处理的,按照行展开,然后顺序存储
int a[2][3] = {
{1,2,3},
{4,5,6}
};
int b[6] = {
{1,2,3,4,5,6}
};
两者在内存中的存储顺序是一致的
---+---+--+---+--+--+-------
a: | 1 | 2 | 3 | 4 | 5 | 6
b: | 1 | 2 | 3 | 4 | 5 | 6
---+---+--+---+--+--+-------
所以在利用二维数组作为参数传递时,必须指定二维数组的列数,行数可写可不写,如下所示
void foo(int a[][3], int m, int n) {
int i = 1;
int j = 1;
printf("a[%d][%d]=%d\n", i, j, a[i][j]);
}
动态存储
C++ 的动态存储分配机制可以在程序运行时建立和撤销对象
new 操作符
动态分配堆内存
指针变量 = new 类型 (常量)
,常量可缺省
指针变量 = new 类型 [表达式]
从堆内存分配一块该类型大小的存储空间,并返回首地址
delete 操作符
释放已分配的内存空间
delete 指针变量
delete [] 指针变量
其中指针变量必须是 new 返回的
应用
int * p1 = new int;
char * p2 = new char;
double * p3 = new double;
int * p4 = new int[4];
delete p1;
delete p2;
delete p3;
delete [] p4;
int * p = NULL;
p = new int (114514); // 该int无名,只能通过p指针访问
字符数组与字符串
C++ 没有字符串类型,以字符数组作为字符串
'\0'
是字符串结束的标志,ascii 码为 0
字符数组作为字符串初始化时,自动添加'\0'
用字符数组存放字符串
char s1[10] = {'s', 't', 'u', 'd', 'e', 'n', 't'};
char s2[10] = {"student"};
char s3[] = {"student"};
char s4[] = "student";
用字符指针管理字符串
char *s = "student";
字符串的访问
iostream 对字符串的扩展功能:
- 字符串常量、字符数组名、字符指针都可以表示字符串
- 输出字符指针就是输出整个字符串
- 数组字符指针的间接引用是输出单个字符
char * s = "student";
cout << s << endl; // 输出 student
cout << *s << endl; // 输出 s
char s[3] = {'a','b','c'};
cout << s << endl; // 输出 abc?@ 后两个为无意义字符,因为找不到 '\0'
static char s[3] = {'a','b','c'};
cout << s << endl; // 正确输出 abc 因为静态数组自动对剩余元素初始化为 '\0'
char s[] = "abc";
cout << s << endl; // 正确输出 abc
输入字符串
char s[10]; // 数组长度不能省略,否则报错
cin >> s;
cout << s << endl;
in: abc
out: abc
特例
char *s = "student";
for(int i = 0; i < 7; i++){
cout << *(s+i) << endl;
}
s
t
u
d
e
n
t
char *s = "student";
for(int i = 0; i < 7; i++){
cout << (s+i) << endl;
}
student
tudent
udent
dent
ent
nt
t
字符串函数
结构
- 结构由固定数目的成员组成
- 各成员可以是不同的数据类型
- 一个结构变量整体,在内存中占有连续的一块内存空间
定义结构
struct 结构名 {
类型 成员1;
类型 成员2;
...
};
定义结构及变量
// 声明类型之后,声明变量
struct student {
char name[10];
int age;
};
student s1;
student s2;
...
// 声明类型同时,声明变量
struct student {
char name[10];
int age;
}s1, s2, *Stu;
// 直接声明变量,且无结构名
struct {
char name[10];
int age;
}s1, s2, *Stu;
// 一个结构,可以是另一结构的成员类型
struct date {
int year;
int month;
int day;
};
struct student {
char name[10];
int age;
date birthday;
}s1, s2;
// 报错,成员类型不能是自身所在的结构类型,即不能是递归结构
struct person {
person father;
person mother;
}
// 声明结构的同时,可以初始化结构变量
struct student {
char name[10];
int age;
}s1 = {"alice", 18};
访问结构
结构变量.成员
如
s1.name
s1.age
指针访问
结构指针->成员
(*结构指针).成员
方式一
#include <iostream>
#include "windows.h"
#include <cstring> // 注意 strcpy() 需要加上文件头
using namespace std;
struct student {
char name[10];
int age;
};
int main(void){
student s1;
student *stuPointer;
stuPointer = &s1;
strcpy(stuPointer->name, "alice");
stuPointer->age = 18;
cout << stuPointer->name << endl;
cout << stuPointer->age << endl;
system("pause");
return 0;
}
alice
18
注意,这里对 name[]
数组赋值必须使用 strcpy()
函数
写成 stuPointer->name = "alice"
则会报错
incompatible types in assignment of 'const char [6]' to 'char [10]'
方式二
#include <iostream>
#include "windows.h"
#include <cstring>
using namespace std;
struct student {
char name[10];
int age;
};
int main(void){
student s1;
student *stuPointer;
stuPointer = &s1;
strcpy((*stuPointer).name, "alice");
(*stuPointer).age = 18;
cout << stuPointer->name << endl;
cout << stuPointer->age << endl;
system("pause");
return 0;
}
类型相同的结构变量可以相互赋值
#include <iostream>
#include "windows.h"
#include <cstring>
using namespace std;
struct student {
char name[10];
int age;
};
int main(void){
student s1;
student s2;
strcpy(s1.name, "alice");
s1.age = 18;
s2 = s1;
cout << s2.name << endl;
cout << s2.age << endl;
system("pause");
return 0;
}
alice
18
结构数组
#include <iostream>
#include "windows.h"
#include <cstring>
using namespace std;
struct student {
char name[10];
int age;
};
int main(void){
student stuArr[3];
strcpy(stuArr[0].name, "alice");
stuArr[0].age = 18;
strcpy(stuArr[1].name, "bob");
stuArr[1].age = 20;
strcpy(stuArr[2].name, "clever");
stuArr[2].age = 22;
for(int i = 0; i < 3; i++){
cout << stuArr[i].name << endl;
cout << stuArr[i].age << endl;
}
system("pause");
return 0;
}
alice
18
bob
20
clever
22
结构内存对齐原则
先看一个例子
struct s1 {
char c1;
int i;
char c2;
};
这个结构体所占用的内存空间大小是 12 字节,而不是 6 字节77
为什么?
因为 c 语言中,结构体需要遵循内存对齐原则
简单来说,对齐数就是取 <变量大小, 8字节>
两者之间的较小者
如 char 变量对齐数是 1
int 变量对齐数是 4
一个占用 16 字节内存空间的结构体变量(嵌套),对齐数是 8
练习
struct s2 {
char c1;
char c2;
int i;
};
s1、s2 的区别仅是变量 i、c2
互换了位置
这个结构体的大小却变成了 8
struct s3 {
double d;
char c;
int i;
};
s3 结构体的大小为 16
struct s3 {
double d;
char c;
int i;
};
struct s4 {
char c1; // 放在 0 号位置
struct s3 s; // 放在 8 号位置
double d; // 放在 24 号位置
}
结构体 s4 的大小为 32
<结构体s3的大小==16, 8>
之间取小者,对齐数为 8
所以 c1
位于 0,s3
位于 8, d
位于 24(8号+16字节)
为什么需要内存对齐?
观点一:可移植性
不是所有的硬件平台都能访问任意地址上的任意数据的
某些硬件平台只能在某些地址处取数据,否则抛出硬件异常
观点二:性能
数据结构(尤其是栈)应该尽可能地在自然边界上对齐
原因在于:
若访问未对齐的内存,处理器需要作两次内存访问
若访问对齐的内存,仅需要一次访问
- Post link: http://example.com/2023/03/03/cpp/
- Copyright Notice: All articles in this blog are licensed under unless otherwise stated.