c++基础
来自视频课程学习笔记资料整理,有做删改,记性不好记录查询使用,仅做参考
一、C++输出数据
数据是信息的载体,写程序的目的就是为了处理数据。
1.数据的分类
数据有数字、字符和字符串三种类型。
- 数字:直接书写,如:100、18.52、0、9;
- 字符:用半角的单引号包含的一个符号,如:'A'、'Z'、'0'、'9'、'~'、'+',汉字和全角的标点符号不是字符;
- 字符串:用半角的双引号包含的多个符号,如:"Hello World"、"我是一只傻鸟"、"西施"、"100"、"X"
2.输出数据
- std::cout可以输出各种类型的数据;
- <<操作符可以拼接多个数据项;
- std::endl也可以用于换行;
- using namespace std;指定缺省的命名空间。
3.示例
#include <iostream> // 指定缺省的命名空间。 using namespace std; int main() { // 用多个数据项输出超女信息,姓名:西施;年龄:25;体重:48.5;性别:X // std::cout << "姓名:西施;年龄:25;体重:48.5;性别:X\n"; cout << "姓名:" << "西施" << ";年龄:" << 25 << ";体重:" << 48.5 << ";性别:" << 'X' << endl; }
二、程序的注释
-
在程序中添加的说明文字,对代码的流程或功能加以解释,方便自己和其他的程序员阅读和理解代码。
-
编译器在编译源代码的时候,会忽略注释。
1.单行注释
用两根反斜线打头,一般放在代码的上方,或者一行语句的末尾。
注意:字符串内容中的两根反斜线是内容的一部分,不是注释。
2.多行注释
从/开始,到/结束,把一些内容夹住。
注意:a)字符串内容中的/和/是内容的一部分,不是注释;b)/和/可以出现在一行代码的中间。
3.注释的注释
单行注释可以注释多行注释,多行注释也可以注释单行注释,但是,不建议使用。
4.VS中的快捷键
添加注释:Ctrl+k+c
取消注释:Ctrl+k+u
5.示例
// 包含头文件。 #include <iostream> using namespace std; // 指定缺省的命名空间。 // main函数,程序从这里开始执行,每个程序只能有一个main函数。 int main() { // 在控制台输出一首诗。 cout << "\n\n 我是一只傻傻鸟\n"; cout << " 生活美好如鲜花,不懂享受是傻瓜;\n"; cout << " 傻呀傻呀傻呀傻,不如小鸟和乌鸦。\n"; cout << " 芳草地啊美如画,谁要不去是傻瓜;\n"; cout << " 我是一只傻傻鸟,独在枯枝丫上趴。\n"; cout << "姓名:" << "西施" << /*";年龄:" << 25 << */";体重:" << 48.5 << "。" << endl; /* std::cout 向控制台输出内容的指令,只能小写,不能用大写。 << 输出的运算符。 "" 字符串内容的边界符,半角。 Hello World! 输出字符串的内容,可以是中文、英文和任意符号,半角的双引号除外。 \n 输出一个换行。 ; C++语句结束的标志,半角。 */ }
三、使用变量
变量是内存变量的简称,用于存放数据。
1.声明变量
变量在使用之前要先声明。
语法:数据类型 变量名
C++常用的数据类型有五种:整数(int)、浮点数(float和double)、字符(char)、字符串(string)和布尔(bool)。
变量在声明的时候可以赋初始值。
语法:数据类型 变量名=值
2.变量的赋值
语法:变量名=值
3.示例
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // main函数,程序从这里开始执行,每个程序只能有一个main函数。 int main() { // 整数(int)、浮点数(float和double)、字符(char)、字符串(string)和布尔(bool)。 // 超女基本信息:姓名、年龄、体重、性别、颜值。 // 声明变量,存放超女基本信息的数据项。 string name = "西施"; // 姓名。 int age = 25; // 年龄。 double weight = 48.6; // 体重(kg)。 char sex = 'X'; // 性别:X-女;Y-男。 bool yz = false; // 颜值:true-漂亮;false-不漂亮。 cout << "姓名:" << name << ",年龄:" << age << ",体重:" << weight << ",性别:" << sex << ",颜值:" << yz << endl; name = "冰冰"; // 字符串有双引号包含。 age = 23; // 整数直接书写。 weight = 50.5; // 浮点数直接书写。 sex = 'X'; // 字符用单引号包含。 yz = true; // 布尔型取值只能是true和false,或1和0。 cout << "姓名:" << name << ",年龄:" << age << ",体重:" << weight << ",性别:" << sex << ",颜值:" << yz << endl; } 注意,布尔型变量输出的时候,如果值是true,将显示1,false显示0。
四、使用常量
常量是程序中固定不变的数据。
1.宏常量
一般在main函数的上面声明,用大写命名。
语法:#define 常量名 值
2.const修饰的变量
在程序的任何地方都可以声明。
语法:const 数据类型 常量名=值
3.常量的特点
程序中不允许改变常量的值,否则编译的时候会报错。
4.示例
#include <iostream> // 包含头文件。 #define MONTHS 12 // 一年中的月份数。 #define PI 3.14159 // 圆周率。 using namespace std; // 指定缺省的命名空间。 // main函数,程序从这里开始执行,每个程序只能有一个main函数。 int main() { const int days = 7; // 一个星期中的天数。 cout << "一年有" << MONTHS << "个月。" << endl; cout << "圆周率的值是:" << PI << endl; cout << "一个星期有" << days << "天。\n"; }
五、标识符的命名
1.C++命名规则
C++规定给标识符(变量、常量、函数、结构体、类等)命名时,必须遵守以下规则。
- 在名称中只能使用字母字符、数字和下划线;
- 名称的第一个字符不能是数字;
- 名称区分大写字符与小写字符;
- 不能将C++关键字用作名称;
- 以下划线和大写字母打头的名称被保留给编译器及其使用的资源使用,如果违反了这一规则,会导致行为的不确定性。
- C++对名称的长度没有限制,但有些平台可能有长度限制(64字符)。
- C++提倡有一定含义的名称(望名知义)。
2.C++关键字
关键字也叫保留字,是C++预先保留的标识符。
每个C++关键字都有特殊的含义,用于声明类型、对象、函数、命名空间等,程序中不能声明与关键字同名的标识符。
asm do if return typedef auto double inline short typeid bool dynamic_cast int signed typename break else long sizeof union case enum mutable static unsigned catch explicit namespace static_cast using char export new struct virtual class extern operator switch voi const false private template volatile const_cast float protected this wchar_t continue for public throw while default friend register true delete goto reinterpret_cast try
六、输入数据
程序输入数据的方式有多种。
- 从控制台的界面中输入(网页、PC桌面程序、APP程序);
- 从文件中读取;
- 从数据库中读取;
- 从网络中读取。
1.用std::cin输入数据
语法:std::cin>>变量名;
注意:
a.布尔型变量的值在计算机内部用1(true)和0(false)存储;
b.程序中可以书写true和false,也可以书写1和0,其它值将强制转换成1;
c.用cin输入时可以填1和0,其它值也强制转换成1;
d.用cout输出时只显示1和0,不显示true和false。
如果输入的数据与变量的数据类型不匹配,会导致行为的不确定性。
2.示例
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // main函数,程序从这里开始执行,每个程序只能有一个main函数。 int main() { string name; // 姓名。 cout << "请输入超女的姓名:"; cin >> name; cout << "输入的超女姓名是:" << name << endl; int age; // 年龄。 cout << "请输入超女的年龄:"; cin >> age; cout << "输入的超女年龄是:" << age << endl; double weight; // 体重(kg)。 cout << "请输入超女的体重(kg):"; cin >> weight; cout << "输入的超女体重是:" << weight << endl; char sex; // 性别:X-女;Y-男。 cout << "请输入超女的性别(X-女;Y-男):"; cin >> sex; cout << "输入的超女性别是:" << sex << endl; bool yz; // 颜值:true-漂亮;false-不漂亮。 cout << "请问输入超女的颜值(1-漂亮;0-不漂亮):"; cin >> yz; cout << "输入的超女颜值是:" << yz << endl; }
七、算术运算
运算符 描述 + 两个数相加。 - 一个数减另一个数。 * 两个数相乘。 / 分子除以分母。 % 取模运算,整除后的余数。
注意:
- 整数进行除法运算时,如果分母为0,程序将异常退出;
- 浮点数进行除法运算时,如果分母为0.0,将得到inf(infinite,无穷大);
- 两个整数进行除法运算时,将舍去小数部分,得到一个整数;
- 整数与浮点数进行除法运算时,得到的结果是浮点数;
- 进行除法运算时,在整数前面加(float)或(double)可以将整数转换为float或double类型;
- 取模运算只能用于整数(分母也不能为0)。
八、自增和自减
- 运算符
- 描述
- ++变量名
- 先把变量的值加1,然后再执行表达式。
- 变量名++
- 先执行表达式,再把变量的值加1。
- --变量名
- 先把变量的值减1,然后再执行表达式。
- 变量名--
- 先执行表达式,再把变量的减1。
九、赋值运算
1.赋值运算
运算符
示例
描述
= c = a + b; 将把a + b的值赋给c。 把右边操作数的值赋给左边操作数。 += c += a; 相当于 c = c + a; 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数。 -= c -= a; 相当于 c = c - a; 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数。 *= c *= a; 相当于 c = c * a; 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数。 /= c /= a; 相当于 c = c / a; 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数。 %= c %= a; 相当于 c = c % a; 求余数且赋值运算符,求两个操作数的模赋值给左边操作数,浮点数不适用取余数。
注意:
- 字符串(string)只能使用等号(=)赋值,不能使用其它的赋值运算符;
- 浮点数不能使用%=运算符;
- 等号(=)赋值运算符可以连续使用;
- 如果赋值运算符两边操作数的数据类型不同,C++将自动转换数据类型,可能会损失精度,也可能超出取值范围,如果转换不成功,编译时会报错。
2.示例
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // main函数,程序从这里开始执行,每个程序只能有一个main函数。 int main() { int a = 10; int c = 20; // c += a; // c=c+a; // c -= a; // c=c-a; // c *= a; // c = c * a; // c /= a; // c = c / a; //c %= a; // c = c % a; // cout << "c=" << c << endl; a = c = 30; cout << "a=" << a<<endl; cout << "c=" << c << endl; }
十、C++11初始化赋值
1.C++和C++11初始化赋值
把值写在小括号中,等于号可以省略(C++标准) int a=(15); // 声明变量a,初始化值为15。 int b(20); // 声明变量b,初始化值为20。 把值写在花括号中,等于号也可以省略(C++11标准),统一初始化列表。 int a={15}; // 声明变量a,初始化值为15。 int b{20}; // 声明变量b,初始化值为20。 **注意**:在Linux平台下,编译需要加-std=c++11参数。
2.示例
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // main函数,程序从这里开始执行,每个程序只能有一个main函数。 int main() { int a = 10; int b = (10); int c(10); int e = {10}; int f{ 10 }; cout << "a=" << a << endl; cout << "b=" << b << endl; cout << "c=" << c << endl; cout << "e=" << e << endl; cout << "f=" << f << endl; }
十一、关系运算
用于比较两个表达式的值,运算的结果为1-true和0-false。
**1.关系运算** 关系 数学的表示 C++的表示 等于 = == 不等于 ≠ != 小于 < < 小于等于 ≤ <= 大于 > > 大于等于 ≥ >=
注意:
- 关系运算符的两边可以是数值,也可以是表达式;
- 用std::cout输出关系运算表达式时,关系表达式要加括号;
- 关系运算也适用于字符串(string),字符串常用的关系运算是==和!=,其它的基本上不用。
2.示例
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // main函数,程序从这里开始执行,每个程序只能有一个main函数。 int main() { int a = 7; int b = 7; bool result; result = a <= b-3; // a和b先做比较(关系)运算,然后再赋值给result。 cout << "result=" << result << endl; // 关系运算表达式需要括号,算术运算表达式可以不用括号。 string str1 = "西施"; string str2 = "西瓜"; cout << (str1 ==> str2) << endl; }
十二、逻辑运算
1.逻辑运算
根据给定的逻辑因子(表达式或值)返回一个新的逻辑因子。
运算符 术语 示例 结果 && 逻辑与 a&&b; 如果a和b都为真,则结果为真,否则为假。 || 逻辑或 a||b; 如果a和b中有一个为真,则结果为真,二者都为假时,结果为假。 ! 逻辑非(反) !a; 如果a为假,则!a为真; 如果a为真,则!a为假。
注意:
- 逻辑运算符的两边可以是数值,也可以是表达式;
- 用std::cout输出逻辑运算表达式时,逻辑表达式要加括号;
- 在实际开发中,逻辑运算的多重组合是重点。
2.示例
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // main函数,程序从这里开始执行,每个程序只能有一个main函数。 int main() { bool a = false; bool b = true; // bool c = a && b; // 如果a和b都为真,则结果为真,否则为假。 // bool c = a || b; // 如果a和b中有一个为真,则结果为真,二者都为假时,结果为假。 bool c = !a; // 如果a为假,则!a为真; 如果a为真,则!a为假。 // cout << "c=" << c << endl; // cout << "a&&b=" << (a && b) << endl; // cout << "a||b=" << (a || b) << endl; cout << "!a=" << c << endl; } /////////////////////////////////////////////////////////////////////////// #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // main函数,程序从这里开始执行,每个程序只能有一个main函数。 int main() { // 超女必须满足四个条件:年龄25岁以下,身高165以上、体重50公重以下,颜值要漂亮。 int age = 23; int height = 168; double weight = 48.8; bool yz = true; cout << "result="<<(age<25&&height>165&&weight<50&&yz==true) << endl; }
十三、逗号运算
1.逗号运算
把一行语句中的多个表达式连接起来,程序将从左到右执行表达式。
语法:表达式一,表达式二,……,表达式n;
逗号运算常用于声明多个变量。
int a,b; // 声明变量a和b。 int a=10,b=20; // 声明变量a和b并初始化。
也可以用于其它语句中,但是,逗号运算符是所有运算符中级别最低的,以下两个表达式的效果是不同的。
int a,b; b=a=2,a*2; b=(a=2,a*2);
2.示例
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // main函数,程序从这里开始执行,每个程序只能有一个main函数。 int main() { /*int a =10, b=20; cout << "a=" << a << endl; cout << "b=" << b << endl;*/ int a, b; //b = a = 2, a * 2; b = (a = 2, a * 2); cout << "a=" << a << endl; cout << "b=" << b << endl; }
十四、if语句的基本结构
语法:
if (表达式) { // 表达式为真时执行的语句。 } else { // 表达式为假时执行的语句。 }
注意:
- if (表达式)之间的空格可以不写。
- 表达式必须使用括号。
- 表达式可以是具体的值。
- 表达式一般是关系运算和逻辑运算表达式,也可以是赋值运算或其它的表达式。
- 不要把if(变量名==值)写成if(变量名=值)。
- 整个if语句可以没有else分支。
- if或else分支花括号内的代码如果只有一行,花括号可以不写;如果一行也没有,花括号一定要写;如果有多行,花括号一定要写,如果不写,除了第一行,其它的代码将不是if语句的一部分。
- if (表达式)和else后不能加分号,因为分号表示空语句。(C++用分号作为一条语句结束的标志。如果在C++程序中不小心多写了一个分号,那么该分号会被当成一条空语句。)
2.示例
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // C++用分号作为一条语句结束的标志。 // 如果在C++程序中不小心多写了一个分号,那么该分号也许会被视作一个空语句。 // 判断超女的颜值,如果漂亮,在控制台输出“晋级下一轮”,否则输出“冲关失败”。 // 1)声明一个bool型变量,用于存放超女的颜值数据。 bool yz; // 2)显示输入超女颜值的提示信息。 cout << "请输入超女的颜值(1-漂亮;0-不漂亮):"; // 3)输入超女的颜值数据,存放在变量中。 cin >> yz; // 4)用if语句判断超女的颜值,如果漂亮,显示“晋级下一轮”,否则显示“冲关失败”。 if (yz == true) { cout << "小姐姐很漂亮哟!" << endl; cout << "恭喜您,晋级下一轮!" << endl; } else { cout << "对不起,冲关失败!" << endl; cout << "推荐一个美容院,......" << endl; } }
十五、嵌套使用if语句
1.嵌套使用if语句
if语句可以嵌套使用,实现多级(层)的条件判断(最多127层)。
语法:
if (表达式一) { if (表达式二) { // 表达式二为真时执行的语句。 } else { // 表达式二为假时执行的语句。 } } else { // 表达式一为假时执行的语句。 }
2.示例
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 超女选秀:1)性别(X-女,Y-男);2)颜值(1-漂亮,0-不漂亮);3)身材(1-火辣,2-丰满,3-苗条)。 // 声明三个变量,存放超女的性别、颜值和身材数据。 char sex; // 性别(X-女,Y-男)。 bool yz; // 颜值(1-漂亮,0-不漂亮)。 int sc; // 身材(1-火辣,2-丰满,3-苗条)。 // 显示“请输入超女的性别(X-女,Y-男):”的提示文字。 cout << "请输入超女的性别(X-女,Y-男):"; // 输入超女的性别,存放在变量中。 cin >> sex; // 判断超女的性别,如果是女,流程继续,否则程序结束。 if (sex == 'X') { // 显示“请输入超女的颜值(1-漂亮,0-不漂亮):”的提示文字。 cout << "请输入超女的颜值(1-漂亮,0-不漂亮):"; // 输入超女的颜值,存放在变量中。 cin >> yz; // 判断超女的颜值,如果是漂亮,流程继续,否则程序结束。 if (yz == true) // 漂亮。 { // 显示“请输入超女的身材(1-火辣,2-丰满,3-苗条):”的提示文字。 cout << "请输入超女的身材(1-火辣,2-丰满,3-苗条):"; // 输入超女的身材,存放在变量中。 cin >> sc; // 判断超女的身材,如果是火辣,显示“晋级成功”,如果是丰满,显示“待定”,程序结束。 if (sc == 1) // 1-火辣。 { cout << "晋级成功!" << endl; } else { if (sc == 2) // 2-丰满 { cout << "待定!" << endl; } } } } }
十六、嵌套if语句的坑
-
嵌套使用if语句时候,会出现if与else的配对问题。
-
C++编译器处理该问题的原则是:else总是与前面最近if 配对。
-
如果要改变这种配对关系,可以使用花括号来确定新的配对关系。
if (sex == 'X') if (yz == true) cout << "sex==X,yz==true\n"; else cout << "sex==Y,yz==true or false\n"; 以上代码中的else与第二个if配对,如果想让它与第一个if配对,代码如下: if (sex == 'X') { if (yz == true) cout << "sex==X,yz==true\n"; } else cout << "sex==Y,yz==true or false\n";
十七、多条件的if语句
1.多条件的if语句
语法:
if (表达式一) { // 表达式一为真时执行的语句。 } else if (表达式二) { // 表达式二为真时执行的语句。 } else if (表达式三) { // 表达式三为真时执行的语句。 } …… else if (表达式n) { // 表达式n为真时执行的语句。 } else { // 全部表达式都不为真时执行的语句。 }
注意:
- 多条件的if语句本质上是嵌套的if语句。
- 最多只能有127个条件分支。
- 最后一个else可以没有。
2.示例
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 超女的身材:1-火辣;2-丰满;3-苗条;4-强壮;5-肥胖;>5-未知 。 // 声明存放超女身材数据的变量。 int sc; // 显示“请输入身材的代码(1-火辣;2-丰满;3-苗条;4-强壮;5-肥胖;>5-未知):”的提示文字。 cout << "请输入身材的代码(1-火辣;2-丰满;3-苗条;4-强壮;5-肥胖;其它表示未知):"; // 输入超女身材的代码,存放在变量中。 cin >> sc; // 用多条件的if语句,判断身材代码,显示身材的中文描述。 if (sc == 1) cout << "火辣!\n"; else if (sc == 2) cout << "丰满!\n"; else if (sc == 3) cout << "苗条!\n"; else if (sc == 4) cout << "强壮!\n"; else if (sc == 5) cout << "肥胖!\n"; else cout << "未知!\n"; }
十八、if语句中的逻辑表达式
采用括号、对齐、空格、换行有助于更清晰的表达复杂的逻辑表达式。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 超女的数据:年龄(16-50岁),身高(150cm-190cm),身材(火辣;丰满;苗条) // 颜值(漂亮;一般;歪瓜裂枣)。 // 晋级的标准:年龄25-30岁,身高165cm-175cm,身材火辣,颜值漂亮或者一般。 // 已准备好的超女数据。 int age = 28; int height = 170; string sc = "火辣"; string yz = "漂亮"; if ( (age > 25 && age < 30 ) && // 年龄 (height > 165 && height < 175 ) && // 身高 (sc == "火辣" ) && // 身材 (yz == "漂亮" || yz == "一般" ) ) // 颜值 { cout << "晋级成功!\n"; } }
十九、三目运算
三目运算也叫条件运算或三元运算,可以实现简单if语句的功能,但是书写更简洁。
语法: 表达式一 ? 表达式二 : 表达式三
先计算表达式一的值,如果为真,整个表达式的结果为表达式二的值,如果为假,整个表达式的结果为表达式三的值。
int a,b,c; a=7; b=6; c=(a>b)?a:b; 等同于 if (a>b) c=a; else c=b;
三目运算表达式可以嵌套使用,但是,过于复杂的三目运算表达式不方便理解。
语法:
switch (表达式) { case 值一: 语句一; break; case 值二: 语句二; break; ...... case 值n: 语句n; break; default: 上述条件都不满足时执行的语句; }
注意:
- case后面必须是整数和字符,或者是结果为整数和字符的表达式,但不能使用变量。
- default不是必须的,当没有default时,如果全部的case匹配失败,那么就什么都不执行。
- 每个分支不要漏写break;语句。
二十、while循环语句
语法:
while (表达式) { 语句块 } 先计算表达式的值,如果为真就执行语句块,执行完语句块后,回到循环首部再次计算表达式的值,如果为真又执行一次语句块……,这个过程会一直重复,直到表达式的值为假时不再执行语句块。
注意:
- 如果表达式的值永远为真,那么将进入死循环,所以在循环中应该有改变表达式的值的方法。
- 如果循环体中的语句块只有一行代码,大括号可以不书写。
- 有疑问先放一边,在以后的课程中,会介绍循环的各种使用方法。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 有十个超女,编号是1-10,在控制台输出这十个超女的编号。 int no = 1; // 超女的编号。 while (no <= 10) { cout << "这是第" << no++ << "名超女的编号。\n"; } }
二十一、循环的跳转
-
break和continue两个关键字用于控制循环体中代码的执行流程。
-
break跳出(中止)当前循环语句。
-
continue回到当前循环语句的首部。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // break跳出(中止)当前循环语句,continue回到当前循环语句的首部。 // 程序运行后一直工作,逐个输入超女的数据,判断是否晋级,如果到了休息时间,就把程序停下来。 // 超女选秀的流程:1)如果漂亮,直接晋级;2)不漂亮也行,身材火辣的也可以晋级。 bool once = true; // 是否为第一次执行循环。 while (true) { if (once == false) { // a)显示“是否继续下一名超女选秀(1-继续,0-结束):”的提示文字。 cout << "是否继续下一名超女选秀(1-继续,0-结束):"; // b)输入是否继续的决定,存放在变量中。 bool exist; cin >> exist; // c)判断输入的决定,如果是结束,流程跳出循环。 if (exist == false) break; } once = false; // 表示循环已经被执行过。 // 1)显示“请输入超女的颜值(1-漂亮,0-不漂亮):”的提示文字。 cout << "请输入超女的颜值(1-漂亮,0-不漂亮):"; // 2)输入超女的颜值,存放在变量中。 bool yz; cin >> yz; // 3)判断超女的颜值,如果漂亮,显示“晋级成功”,流程跳转到循环的首部。 if (yz == true) { cout << "晋级成功\n"; continue; } // 4)显示“请输入超女的身材(1-火辣,0-不辣):”的提示文字。 cout << "请输入超女的身材(1-火辣,0-不辣):"; // 5)输入超女的身材,存放在变量中。 bool sc; cin >> sc; // 6)判断超女的身材,如果火辣,显示“晋级成功”。 if (sc == true) cout << "晋级成功\n"; } }
二十二、for循环语句
语法:
for (语句一 ; 表达式 ; 语句二) { 语句块 }
1.循环开始的时候,先执行语句一,在整个循环过程中语句一只会被执行一次。
2.计算表达式的值,如果为真,就执行一次循环体中的语句块。
3.执行完语句块后,执行一次语句二。
4.重复第2)步和第3),直到表达式的值不为真才结束for循环。
注意:
- 不要纠结for循环与while循环的区别,它们本质上没有区别。
- for循环一般需要一个相当于计数器的变量,在语句一中对它进行初始化,在语句二中进行计数操作。
- 在for循环的语句一中,可以声明计数器变量。
- 在for循环中,语句一、表达式和语句二都可以为空,for (;;)等同于while (true)。
- continue和break两个关键字也可以用在for循环体中。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 有十个超女,编号是1-10,在控制台输出这十个超女的编号。 for (int no = 1; no <= 10; no++) { cout << "这是第" << no << "名超女的编号。\n"; } }
二十三、嵌套使用循环
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 超女分4个小组,每个小组有3名超女,在控制台显示每个超女的小组编号和组内编号。 // 用一个循环,显示4个小组的信息。 //for (int ii=1; ii<=4; ii++) //{ // // 再用一个循环,显示一组中3名超女的信息。 // for (int jj = 1; jj <= 3; jj++) // { // cout << "这是第" <<ii<<"个小组的第" << jj << "名超女。\n"; // } //} // 在控制台输出九九乘法表。 for (int ii=1; ii<=9; ii++) { for (int jj = 1; jj <= ii; jj++) { cout << ii << "*" << jj << "=" << ii*jj <<" "; } cout << endl; } }
二十四、do…while循环语句
语法:
do { 语句块 } while (表达式);
功能与while语句类似,不同的是:
- 进入循环时,先执行一次语句块,再计算表达式的值。
- 循环的首部书写在循环的尾部,(表达式)后面还有一个分号。
二十五、goto语句
-
goto语句也称为无条件转移语句。
-
goto的语法:goto 语句标号;
-
语句标号的语法:语句标号:
-
如果在程序中使用了goto,程序的流程将跳转到语句标号的位置,并执行它后面的代码。
-
其中语句标号是按标识符规定书写的符号,放在某一语句行的前面,可以独占一行,标号后加半角冒号。
-
语句标号用于标识语句的位置,与goto语句配合使用。
-
在实际开发中,goto语句容易造成程序流程的混乱,不方便理解,调试也更麻烦,不建议使用。
二十六、函数的声明和定义
-
在复杂的程序中,如果全部的代码都写在main函数中,main函数体将非常庞大臃肿。
-
把任务分工到其它的函数中,main函数只负责程序的核心流程,具体的任务由其它函数完成。
-
这种思想就是模块化编程。
声明和定义函数的语法:
返回值的数据类型 函数名(参数一的数据类型 参数一, 参数二的数据类型 参数二,……) { 实现函数功能的代码。 return 返回值; } 函数的声明:让编译器知道函数的存在,包括返回值的数据类型、函数名和参数列表。 函数的定义:函数的实现过程。
注意:
- 函数的声明和定义可以书写在一起,也可以分开,如果书写在一起,一般放在main函数的上面,如果分开,一般在main函数的上面声明,在main函数的下面定义。
- 如果函数的声明和定义分开书写,函数的声明后面一定要有分号,函数的定义后面一定不能写分号。
- 在同一个程序中,函数只需要声明和定义一次,也可以多次声明,但只能定义一次。
- 函数的声明必须和函数的定义一致(返回值的数据类型、函数名和参数列表),如果函数名和参数列表不同,表示它们不是同一个函数。
- return语句返回值的数据类型必须与函数的声明一致。
- 在函数体中,return语句可以多次使用。
- 如果函数的重点是实现功能,不关心返回值,返回值的数据类型填void,return语句后面就空着。
- 函数可以没有任何参数。
- 函数名是标识符,必须满足标识符的命名规则。
- 在函数的声明和函数的定义中,参数命名可以不同,但是没必要这么书写。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // 写一个函数,给它两个整数,让它比较两个整数的大小,返回较大的那个整数。 int max(int a, int b); // 函数声明后面的分号不能少。 // 写一个函数,给它一个字符串,让它在控制台显示出来。 void print(string str); // 写一个函数,在控制台输出九九乘法表。 void printmt(); int main() { } int max(int a, int b) // 函数定义后面不能加分号。 { if (a > b) return a; return b; } void print(string str) { cout << str << endl; return; } void printmt() { // 在控制台输出九九乘法表。 for (int ii = 1; ii <= 9; ii++) { for (int jj = 1; jj <= ii; jj++) { cout << ii << "*" << jj << "=" << ii * jj << " "; } cout << endl; } return; }
二十七、函数的调用
语法:函数名(参数一,参数二,……)
注意:
- 声明函数的代码必须放在调用之前,定义函数的代码可以放在调用之后。
- 调用函数的时候,参数列表必须与函数的声明一致(参数的个数、书写的顺序和数据类型)。
- 不管在什么地方,都不能调用main函数,但是,在普通函数中,可以调用其它的普通函数。
- 调用函数的代码可以独占一条语句,也可以用于表达式(赋值运算、算术运算、关系运算、函数的参数)。
- 如果函数用于表达式中,返回值的数据类型要匹配(否则可能会被隐式转换或编译错误)。
- 如果函数有返回值,可以不关心它,忽略它。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // 写一个函数,给它两个整数,让它比较两个整数的大小,返回较大的那个整数。 int max(int a, int b); // 函数声明后面的分号不能少。 // 写一个函数,给它两个参数:no-超女编号,str-表白的内容。 void print(int no,string str); // 写一个函数,在控制台输出九九乘法表。 void printmt(); int main() { cout << "max(5,8)=" << max(5,8) << endl; print(8, "请借给我五分钱吧。"); printmt(); } int max(int a, int b) // 函数定义后面不能加分号。 { if (a > b) return a; return b; } void print(int no, string str) { cout << "亲爱的"<<no<<"号:"<<str << endl; return; } void printmt() { // 在控制台输出九九乘法表。 for (int ii = 1; ii <= 9; ii++) { for (int jj = 1; jj <= ii; jj++) { cout << ii << "*" << jj << "=" << ii * jj << " "; } cout << endl; } return; }
二十八、变量的作用域
作用域是指程序中变量存在(或生效)的区域,超过该区域变量就不能被访问。
变量分全局变量和局部变量两种,全局变量在整个程序中都可以访问,局部变量只能在函数或语句块的内部才能访问。
C++中定义变量的场景主要有五种:
1.在全部函数外面定义的是全局变量。
2.在头文件中定义的是全局变量。
3.在函数和语句块内部定义的是局部变量。
4.函数的参数是该函数的局部变量。
5.函数内部用static修饰的是静态局部变量。
1.全局变量
在整个程序生命周期内都是有效的,在定义位置之后的任意函数中都能访问。
全局变量在主程序退出时由系统收回内存空间。
2.局部变量
在函数或语句块内部的语句使用,在函数或语句块外部是不可用的。
局部变量在函数返回或语句块结束时由系统收回内存空间。
3.静态局部变量
用static修饰的局部变量生命周期和程序相同,并且只会被初始化一次。
其作用域为局部,当定义它的函数或语句块结束时,其作用域随之结束。
当程序想要使用全局变量的时候应该先考虑使用static(考虑到数据安全性)。
4.注意事项
- 全局变量和静态局部变量自动初始化为0。
- 局部变量不会自动初始化,其值是不确定的,程序中应该有初始化局部变量的代码,否则编译可能会报错(不同的编译器不一样)。
- 局部变量和全局变量的名称可以相同,在某函数或语句块内部,如果局部变量名与全局变量名相同,就会屏蔽全局变量而使用局部变量,如果想使用全局变量,可以在变量名前加两个冒号(::)。
- for循环初始化语句中定义的变量的作用域是for语句块。
二十九、函数参数的传递
调用函数的时候,调用者把数值赋给了函数的参数。
实参:调用者程序中书写的在函数名括号中的参数,可以是常量、变量和表达式。
形参:函数的参数列表。
在函数定义的代码中,修改形参的值,会不会影响实参。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // 调用函数的时候,调用者把数值赋给了函数的参数。 // 实参:调用者程序中书写的在函数名括号中的参数,可以是常量、变量和表达式。 // 形参:函数的参数列表。 // 在函数定义的代码中,修改形参的值,会不会影响实参? void func(int no, string str); // 向超女表白的函数。 int main() { int bh = 3; // 超女的编号。 string message = "我是一只傻傻鸟。"; // 向超女表白的内容。 // func(bh, message); // 调用向超女表白的函数。 // func(7, "小姐姐好漂亮哟。"); // 调用向超女表白的函数。 { int no=7; string str= "小姐姐好漂亮哟。"; no = 5; str = "我有一只小小鸟。"; cout << "亲爱的" << no << "号:" << str << endl; } cout << "亲爱的" << bh << "号:" << message << endl; } void func(int no, string str) // 向超女表白的函数。 { no = 5; str = "我有一只小小鸟。"; cout << "亲爱的" << no << "号:" << str << endl; }
三十、函数分文件编写
头文件(*.h):需要包含的头文件,声明全局变量,函数的声明,数据结构和类的声明等。
源文件(*.cpp):函数的定义、类的定义。
主程序:main函数,程序的核心流程,需要用#include "头文件名"把头文件包含进来。
编译:
Windows是集成开发环境,不需要写编译指令。
在Linux系统下,把全部的源文件一起编译,如:g++ -o demo demo.cpp tools.cpp girls.cpp
示例:
/*demo01.cpp*/ #include "tools.h" // 包含头文件tools.h,min和max函数在里面。 #include "girls.h" // 包含头文件girls.h,print函数在里面。 int main() { cout << "max(5,8)=" << max(5, 8) << endl; cout << "min(5,8)=" << min(5, 8) << endl; print(3, "我是一只傻傻鸟。"); } /*girls.cpp*/ #include "girls.h" void print(int no, string str) // 表白神器。 { cout << "亲爱的" << no << "号:" << str << endl; } /*tools.cpp*/ #include "tools.h" int max(int a, int b) // 比较两个数的大小,返回较大者。 { return a > b ? a : b; } int min(int a, int b) // 比较两个数的大小,返回较小者。 { return a < b ? a : b; } /*girls.h*/ #pragma once #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void print(int no, string str); // 表白神器。 /*toolss.h*/ #pragma once #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int max(int a, int b); // 比较两个数的大小,返回较大者。 int min(int a, int b); // 比较两个数的大小,返回较小者。
三十一、VS中调试程序
-
F9设置/取消断点。
-
F5/F10开始调试。
-
Shift+F5放弃调试。
-
F10逐过程执行。
-
F11逐语句执行(可进入函数内部)。
-
局部变量窗口显示了变量的值,也可以修改。
三十二、递归函数
一个函数可以调用另一个函数,作为特例,如果函数调用了自己,就像故事中提到了同样的故事一样,我们把函数在运行时调用自己的情况叫做递归。
递归函数中一定要有递归终止的条件,否则是死递归。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int f(int x) // 递归函数。 { if (x == 0) return 0; // 递归终止的条件。 return x + f(x - 1); // 在函数体中调用了自己。 } int main() { cout << "f(100)=" << f(100) << endl; // 100+99+98+....+1+0 // 嵌套的调用函数 进入函数的过程是递去 函数返回的过程是归来 // 计算从1加到100的和。 int sum = 0; // 存放累加的值。 for (int ii = 1; ii <= 100; ii++) sum = sum + ii; cout << "sum=" << sum << endl; }
三十三、sizeof运算符
-
sizeof运算符用于求数据类型或变量占用的内存空间。
-
用于数据类型:sizeof(数据类型)
-
用于变量:sizeof(变量名) 或 sizeof 变量名
注意:
在32位和64位操作系统中,同一种数据类型占用的内存空间可能不一样。
字符串(string)不是C++的基本数据类型,用sizeof求它占用内存的大小没有意义。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 用于数据类型:sizeof(数据类型) // 用于变量:sizeof(变量名) 或 sizeof 变量名 // C++常用的数据类型:整数(int)、浮点数(float和double)、字符(char)和布尔(bool)。 cout << "sizeof(int)=" << sizeof(int) << endl; cout << "sizeof(float)=" << sizeof(float) << endl; cout << "sizeof(double)=" << sizeof(double) << endl; cout << "sizeof(char)=" << sizeof(char) << endl; cout << "sizeof(bool)=" << sizeof(bool) << endl; int i; cout << "sizeof(int)=" << sizeof i << endl; float f; cout << "sizeof(float)=" << sizeof f << endl; double d; cout << "sizeof(double)=" << sizeof d << endl; char c; cout << "sizeof(char)=" << sizeof c << endl; bool b; cout << "sizeof(bool)=" << sizeof b << endl; }
三十四、数据类型的转换
-
计算机进行运算时,要求各操作数的类型具有相同的大小和存储方式。
-
在实际开发中,不同类型的数据进行混合运算是基本需求。
-
自动类型转换:某些类型的转换编译器可以隐式的进行,不需程序员干预。
-
强制类型转换:有些类型的转换需要程序员显式指定。
1.自动类型转换
不同数据类型的差别在于取值范围和精度,数据的取值范围越大,精度越高。
整型从低到高:
char -> short -> int -> long -> long long
浮点型从低到高:
float -> double -> long double
自动类型转换的规则如下:
如果一个表达式中出现了不同类型操作数的混合运算,较低类型将自动向较高类型转换。
当表达式中含有浮点型操作数时,所有操作数都将转换为浮点型。
赋值运算的右值类型与左值类型不一致时,将右值类型提升/降低为左值类型。
赋值运算右值超出了左值类型的表示范围,把该右值截断后赋给左值,所得结果可能毫无意义。
2.强制类型转换
为了让程序设计更灵活,转换的目的更清晰,C++提供了强制类型转换的方法,也称之为显式转换。
强制类型转换的语法:(目标类型)表达式或目标类型(表达式)
注意:
- 如果使用强制转换,表示程序员已有明确的目的。
- 如果转换的行为不符合理,后果由程序员承担。
- 如果采用了强制类型转换,编译的告警信息将不再出现。
- 类型转换运算符的优先级比较高,如果没把握就加括号。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { char a = 30; int b = 102400; long long c = 15000000000001; // 如果一个表达式中出现了不同类型操作数的混合运算,较低类型将自动向较高类型转换。 cout << "a+b+c=" << a + b + c << endl; // 当表达式中含有浮点型操作数时,所有操作数都将转换为浮点型。 cout << "8/5=" << ((double)8) / 5 << endl; // 赋值运算的右值类型与左值类型不一致时,将右值类型提升/降低为左值类型。 // 赋值运算右值超出了左值类型的表示范围,把该右值截断后赋给左值,所得结果可能毫无意义。 int d = (int)23.59; // 降低了精度。 cout << "d=" << d << endl; unsigned int e = (unsigned int)4294967295+10; // 值被截断,从高位截断 cout << "e=" << e << endl; // 4294967295 11111111111111111111111111111111 // 4294967296 000100000000000000000000000000000000 // 4294967297 000100000000000000000000000000000001 }
三十五、数据类型的别名typedef
创建数据类型的别名有两个目的:
-
为名称复杂的类型创建别名,方便书写和记忆。
-
创建与平台无关的数据类型,提高程序的兼容性。
-
语法:typedef 原数据类型名 别名;
-
C++11还可以用using关键字创建数据类型的别名。
语法:using 别名=原数据类型名;
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 1)为名称复杂的类型创建别名,方便书写和记忆。 // 2)创建与平台无关的数据类型,提高程序的兼容性。 // 在VS中,short是两个字节,int是四个字节,long也是四个字节,long long是八个字节。 typedef short int16_t; // 16位的整数。 typedef int int32_t; // 32位的整数。 typedef long long int64_t; // 64位的整数。 // 在Linux中,short是两个字节,int是四个字节,long也是八个字节,long long也是八个字节。 typedef short int16_t; // 16位的整数。 typedef int int32_t; // 32位的整数。 typedef long int64_t; // 64位的整数。 // 在程序源代码中,只使用别名int16_t、int32_t、int64_t,不使用原名。 }
三十六、指针的基本概念
1.变量的地址
-
变量是内存变量的简称,在C++中,每定义一个变量,系统就会给变量分配一块内存,内存是有地址的。
-
C++用运算符&获取变量在内存中的起始地址。
语法:&变量名
2.指针变量
指针变量简称指针,它是一种特殊的变量,专用于存放变量在内存中的起始地址。
语法:数据类型 *变量名;
-
数据类型必须是合法的C++数据类型(int、char、double或其它自定义的数据类型)。
-
星号*与乘法中使用的星号是相同的,但是,在这个场景中,星号用于表示这个变量是指针。
3.对指针赋值
- 不管是整型、浮点型、字符型,还是其它的数据类型的变量,它的地址都是一个十六进制数。我们用整型指针存放整数型变量的地址;用字符型指针存放字符型变量的地址;用浮点型指针存放浮点型变量的地址,用自定义数据类型指针存放自定义数据类型变量的地址。
语法:指针=&变量名;
注意
-
对指针的赋值操作也通俗的被称为“指向某变量”,被指向的变量的数据类型称为“基类型”。
-
如果指针的数据类型与基类型不符,编译会出现警告。但是,可以强制转换它们的类型。
4.指针占用的内存 -
指针也是变量,是变量就要占用内存空间。
-
在64位的操作系统中,不管是什么类型的指针,占用的内存都是8字节。
-
在C++中,指针是复合数据类型,复合数据类型是指基于其它类型而定义的数据类型,在程序中,int是整型类型,int是整型指针类型,int可以用于声明变量,可以用于sizeof运算符,可以用于数据类型的强制转换,总的来说,把int*当成一种数据类型就是了。
三十七、使用指针
-
声明指针变量后,在没有赋值之前,里面是乱七八糟的值,这时候不能使用指针。
-
指针存放变量的地址,因此,指针名表示的是地址(就像变量名可以表示变量的值一样)
-
运算符被称为间接值或解除引用(解引用)运算符,将它用于指针,可以得到该地址的内存中存储的值,也是乘法符号,C++根据上下文来确定所指的是乘法还是解引用。
-
变量和指向变量的指针就像同一枚硬币的两面。
哪个银行? 什么东西? 数额
程序在存储数据的时候,必须跟踪三种基本属性:
数据存储在哪里;
数据是什么类型;
数据的值是多少。
用两种策略可以达到以上目的:
声明一个普通变量,声明时指出数据类型和变量名(符号名),系统在内部跟踪该内存单元。
声明一个指针变量,存储的值是地址,而不是值本身,程序直接访问该内存单元。
三十八、指针用于函数的参数
如果把函数的形参声明为指针,调用的时候把实参的地址传进去,形参中存放的是实参的地址,在函数中通过解引用的方法直接操作内存中的数据,可以修改实数的值,这种方法被通俗的称为地址传递或传地址。
值传递:函数的形参是普通变量。
传地址的意义如下:
可以在函数中修改实参的值。
减少内存拷贝,提升性能。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // 调用函数的时候,调用者把数值赋给了函数的参数。 // 实参:调用者程序中书写的在函数名括号中的参数。 // 形参:函数的参数列表。 void func(int *no, string *str) // 向超女表白的函数。 { cout << "亲爱的" << *no << "号:" << *str << endl; *no = 8; *str = "我有一只小小鸟。"; } // 写一个函数,从3名超女的身高数据中,选出最高的和最矮的。 void func1(int a, int b, int c, int* max, int* min) { *max = a > b ? a : b; // 取a和b中的大者。 *min = a < b ? a : b; // 取a和b中的小者。 *max = *max > c ? *max : c; // 取*max和c中的大者。 *min = *min < c ? *min : c; // 取*min和c中的大者。 } int main() { int bh = 3; // 超女的编号。 string message = "我是一只傻傻鸟。"; // 向超女表白的内容。 func(&bh, &message); // 调用向超女表白的函数。 /*{ int *no = &bh; string *str = &message; cout << "亲爱的" << *no << "号:" << *str << endl; *no = 8; *str = "我有一只小小鸟。"; }*/ cout << "亲爱的" << bh << "号:" << message << endl; // 从3名超女的身高数据中,选出最高的和最矮的。 int a = 180, b = 170, c = 175, m, n; func1(a, b, c, &m, &n); cout << "m=" << m << ",n=" << n << endl; }
三十九、用const修饰指针
1.常量指针
语法:const 数据类型 *变量名;
不能通过解引用的方法修改内存地址中的值(用原始的变量名是可以修改的)。
注意:
- 指向的变量(对象)可以改变(之前是指向变量a的,后来可以改为指向变量b)。
- 一般用于修饰函数的形参,表示不希望在函数里修改内存地址中的值。
- 如果用于形参,虽然指向的对象可以改变,但这么做没有任何意义。
- 如果形参的值不需要改变,建议加上const修饰,程序可读性更好。
2.指针常量
语法:数据类型 * const 变量名;
指向的变量(对象)不可改变。
注意:
- 在定义的同时必须初始化,否则没有意义。
- 可以通过解引用的方法修改内存地址中的值。
- C++编译器把指针常量做了一些特别的处理,改头换面之后,有一个新的名字,叫引用。
3.常指针常量
语法:const 数据类型 * const 变量名;
指向的变量(对象)不可改变,不能通过解引用的方法修改内存地址中的值。
常引用。
常量指针:指针指向可以改,指针指向的值不可以更改。
指针常量:指针指向不可以改,指针指向的值可以更改。
常指针常量:指针指向不可以改,指针指向的值不可以更改。
记忆秘诀:*表示指针,指针在前先读指针;指针在前指针就不允许改变。
常量指针:const 数据类型 *变量名
指针常量:数据类型 * const 变量名
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void func(const int *no, const string *str) // 向超女表白的函数。 { // *no = 8; // *str = "我有一只小小鸟。"; cout << "亲爱的" << *no << "号:" << *str << endl; } int main() { int a = 3, b = 8; // 常量指针的语法:const 数据类型* 变量名; // 不能通过解引用的方法修改内存地址中的值(用原始的变量名是可以修改的)。 /*const int* p = &a; a = 13; cout << "a=" << a << ",*p=" << *p << endl; p = &b; cout << "b=" << b << ",*p=" << *p << endl;*/ // 指针常量语法:数据类型* const 变量名; // 指向的变量(对象)不可改变;在定义的同时必须初始化;可以通过解引用的方法修改内存地址中的值。 int* const p=&a; *p = 13; cout << "a=" << a << ",*p=" << *p << endl; //int bh = 3; // 超女的编号。 //string message = "我是一只傻傻鸟。"; // 向超女表白的内容。 // //func(&bh, &message); // 调用向超女表白的函数。 //cout << "亲爱的" << bh << "号:" << message << endl; }
四十、void关键字
在C++中,void表示为无类型,主要有三个用途:
1.函数的返回值用void,表示函数没有返回值。
void func(int a,int b) { // 函数体代码。 return; }
2.函数的参数填void,表示函数不需要参数(或者让参数列表空着)。
int func(void) { // 函数体代码。 return 0; }
3.的形参用void *,表示接受任意数据类型的指针。
注意:
- 不能用void声明变量,它不能代表一个真实的变量,但是,用void *可以。
- 不能对void *指针直接解引用(需要转换成其它类型的指针)。
- 把其它类型的指针赋值给void*指针不需要转换。
- 把void *指针赋值给把其它类型的指针需要转换。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // 只关心地址本身,不关心里面的内容,用void *可以存放任意类型的地址。 // 显示变量的十六进制地址的函数:varname-变量名,p-变量的地址。 void func(string varname, void* p) { cout << varname<< "的地址是:" << p << endl; cout << varname << "的值是:" << *(char *)p << endl; } int main() { int a=89; char b='X'; cout << "a的地址是:" << & a << endl; cout << "b的地址是:" << & b << endl; func("a", &a); func("b", & b); }
四十一、C++内存模型
在 C++ 中,程序运行时,内存主要分成四个区,分别是栈、堆、数据段和代码段。
-
栈:存储局部变量、函数参数和返回值。
-
堆:存储动态开辟内存的变量。
-
数据段:存储全局变量和静态变量。
-
代码段:存储可执行程序的代码和常量(例如字符常量),此存储区不可修改。
栈和堆的主要区别:
1.管理方式不同:栈是系统自动管理的,在出作用域时,将自动被释放;堆需手动释放,若程序中不释放,程序结束时由操作系统回收。
2.空间大小不同:堆内存的大小受限于物理内存空间;而栈就小得可怜,一般只有8M(可以修改系统参数)。
3.分配方式不同:堆是动态分配;栈有静态分配和动态分配(都是自动释放)。
4.分配效率不同:栈是系统提供的数据结构,计算机在底层提供了对栈的支持,进栈和出栈有专门的指令,效率比较高;堆是由C++函数库提供的。
5.是否产生碎片:对于栈来说,进栈和出栈都有着严格的顺序(先进后出),不会产生碎片;而堆频繁的分配和释放,会造成内存空间的不连续,容易产生碎片,太多的碎片会导致性能的下降。
6.增长方向不同:栈向下增长,以降序分配内存地址;堆向上增长,以升序分配内存地址。
四十二、动态分配内存new和delete
使用堆区的内存有四个步骤:
1.声明一个指针;
2.用new运算符向系统申请一块内存,让指针指向这块内存;
3.通过对指针解引用的方法,像使用变量一样使用这块内存;
4.如果这块内存不用了,用delete运算符释放它。
申请内存的语法:new 数据类型(初始值); // C++11支持{}
如果申请成功,返回一个地址;如果申请失败,返回一个空地址(暂时不考虑失败的情况)。
释放内存的语法:delete 地址;
释放内存不会失败(还钱不会失败)。
注意:
- 动态分配出来的内存没有变量名,只能通过指向它的指针来操作内存中的数据。
- 如果动态分配的内存不用了,必须用delete释放它,否则有可能用尽系统的内存。
- 动态分配的内存生命周期与程序相同,程序退出时,如果没有释放,系统将自动回收。
- 就算指针的作用域已失效,所指向的内存也不会释放。
- 用指针跟踪已分配的内存时,不能跟丢。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 1)声明一个指针; // 2)用new运算符向系统申请一块内存,让指针指向这块内存; // 3)通过对指针解引用的方法,像使用变量一样使用这块内存; // 4)如果这块内存不用了,用delete运算符释放它。 // 申请内存的语法:new 数据类型(初始值); // C++11支持{} // 释放内存的语法:delete 地址; int* p = new int(5); cout << "*p=" << *p << endl; *p = 8; cout << "*p=" << *p << endl; delete p; /* for (int ii = 1; ii > 0; ii++) { int* p = new int[100000]; // 一次申请100000个整数,这个语法以后再讲。 cout << "ii="<<ii<<",p=" << p << endl; }/* }
四十三、二级指针
-
指针是指针变量的简称,也是变量,是变量就有地址。
-
指针用于存放普通变量的地址。
-
二级指针用于存放指针变量的地址。
-
声明二级指针的语法:数据类型** 指针名;
-
使用指针有两个目的:1)传递地址;2)存放动态分配的内存的地址。
-
在函数中,如果传递普通变量的地址,形参用指针;传递指针的地址,形参用二级指针。
-
把普通变量的地址传入函数后可以在函数中修改变量的值;把指针的地址传入函数后可以在函数中指针的值。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void func(int **pp) { *pp = new int(3); cout << "pp=" << pp << ",*pp=" << *pp << endl; } int main() { /*int ii = 8; cout << "ii=" << ii << ",ii的地址是:" << &ii << endl; int* pii = ⅈ cout << "pii=" << pii << ",pii的地址是:" << &pii << ",*pii=" << *pii << endl; int** ppii = &pii; cout << "ppii=" << ppii << ",ppii的地址是:" << &ppii << ",*ppii=" << *ppii << endl; cout << "**ppii=" << **ppii << endl;*/ int* p=0; func(&p); /*{ int** pp = &p; *pp = new int(3); cout << "pp=" << pp << ",*pp=" << *pp << endl; }*/ cout << "p=" << p << ",*p=" << *p << endl; }
四十四、空指针
在C和C++中,用0或NULL都可以表示空指针。
声明指针后,在赋值之前,让它指向空,表示没有指向任何地址。
1.使用空指针的后果
-
如果对空指针解引用,程序会崩溃。
-
如果对空指针使用delete运算符,系统将忽略该操作,不会出现异常。所以,内存被释放后,也应该把指针指向空。
-
在函数中,应该有判断形参是否为空指针的代码,目的是保证程序的健壮性。
-
为什么空指针访问会出现异常?
-
NULL指针分配的分区:其范围是从 0x00000000到0x0000FFFF。这段空间是空闲的,对于空闲的空间而言,没有相应的物理存储器与之相对应,所以对这段空间来说,任何读写操作都是会引起异常的。空指针是程序无论在何时都没有物理存储器与之对应的地址。为了保障“无论何时”这个条件,需要人为划分一个空指针的区域,固有上面NULL指针分区。
2.C++11的nullptr
-
用0和NULL表示空指针会产生歧义,C++11建议用nullptr表示空指针,也就是(void *)0。
-
NULL在C++中就是0,这是因为在C++中void* 类型是不允许隐式转换成其他类型的,所以之前C++中用0来代表空指针,但是在重载整形的情况下,会出现上述的问题。所以,C++11加入了nullptr,可以保证在任何情况下都代表空指针,而不会出现上述的情况,因此,建议用nullptr替代NULL吧,而NULL就当做0使用。
注意:在Linux平台下,如果使用nullptr,编译需要加-std=c++11参数。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void func(int* no, string* str) // 向超女表白的函数。 { if ((no == 0) || (str == 0)) return; cout << "亲爱的" << *no << "号:" << *str << endl; } int main() { // int bh = 3; // 超女的编号。 // string message = "我是一只傻傻鸟。"; // 向超女表白的内容。 int* bh = 0; // new int(3); string* message = 0; // new string("我是一只傻傻鸟。"); func(bh,message); // 调用向超女表白的函数。 delete bh; delete message; }
四十五、野指针
野指针就是指针指向的不是一个有效(合法)的地址。
在程序中,如果访问野指针,可能会造成程序的崩溃。
出现野指针的情况主要有三种:
1.指针在定义的时候,如果没有进行初始化,它的值是不确定的(乱指一气)。
2.如果用指针指向了动态分配的内存,内存被释放后,指针不会置空,但是,指向的地址已失效。
3.指针指向的变量已超越变量的作用域(变量的内存空间已被系统回收),让指针指向了函数的局部变量,或者把函数的局部变量的地址作为返回值赋给了指针。
规避方法:
1.指针在定义的时候,如果没地方指,就初始化为nullptr。
2.动态分配的内存被释放后,将其置为nullptr。
3.函数不要返回局部变量的地址。
注意:野指针的危害比空指针要大很多,在程序中,如果访问野指针,可能会造成程序的崩溃。是可能,不是一定,程序的表现是不稳定,增加了调试程序的难度。
四十六、函数指针
函数的二进制代码存放在内存四区中的代码段,函数的地址是它在内存中的起始地址。如果把函数的地址作为参数传递给函数,就可以在函数中灵活的调用其它函数。
使用函数指针的三个步骤:
a.声明函数指针;
b.让函数指针指向函数的地址;
c.通过函数指针调用函数。
1.声明函数指针
声明普通指针时,必须提供指针的类型。同样,声明函数指针时,也必须提供函数类型,函数的类型是指返回值和参数列表(函数名和形参名不是)
假设函数的原型是:
int func1(int bh,string str); int func2(int no,string message); int func3(int id,string info); bool func4(int id,string info); bool func5(int id);
则函数指针的声明是:
int (*pfa)(int,string); bool (*pfb)(int,string); bool (*pfc)(int); pfa、pfb、pfc是函数指针名,必须用括号,否则就成了返回指针的函数。
2.函数指针的赋值
函数名就是函数的地址。
函数指针的赋值:函数指针名=函数名;
3.函数指针的调用
(*函数指针名)(实参);
函数指针名(实参);
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void func(int no, string str) { cout << "亲爱的" << no << "号:" << str << endl; } int main() { int bh = 3; // 超女的编号。 string message = "我是一只傻傻鸟。"; // 向超女表白的内容。 func(bh, message); void (*pfunc)(int, string); // 声明表白函数的函数指针。 pfunc = func; // 对函数指针赋值,语法是函数指针名=函数名。 pfunc(bh, message); // 用函数指针名调用函数。 C++ (*pfunc)(bh, message); // 用函数指针名调用函数。 C语言 } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void zs(int a) // 张三的个性化表白函数。 { cout <<"a=" << a << "我要先翻三个跟斗再表白。\n"; // 个性化表白的代码。 } void ls(int a) // 李四的个性化表白函数。 { cout << "a=" << a << "我有一只小小鸟。\n"; // 个性化表白的代码。 } void show(void (*pf)(int),int b) { cout << "表白之前的准备工作已完成。\n"; // 表白之前的准备工作。 pf(b); // 用函数指针名调用个性化表白函数。 cout << "表白之后的收尾工作已完成。\n"; // 表白之后的收尾工作。 } int main() { show(zs, 3); // 张三要表白。 show(ls, 4); // 李四要表白。 }
四十七、一维数组的基本概念
数组是一组数据类型相同的变量,可以存放一组数据。
1.创建数组
声明数组的语法:数据类型 数组名[数组长度];
注意:数组长度必须是整数,可以是常量,也可以是变量和表达式。
C90规定必须用常量表达式指明数组的大小,C99允许使用整型非常量表达式。经测试,在VS中可以用用整型非常量表达式,不能用变量;但是,Linux中还可以用变量。
2.数组的使用
可以通过下标访问数组中元素,数组下标从0开始。
数组中每个元素的特征和使用方法与单个变量完全相同。
语法:数组名[数组下标]
注意:
数组下标也必须是整数,可以是常量,也可以是变量。
合法的数组下标取值是:0~(数组长度-1)。
3.数组占用内存的情况
数组在内存中占用的空间是连续的。
用sizeof(数组名)可以得到整个数组占用内存空间的大小(只适用于C++基本数据类型)。
4.数组的初始化
声明的时候初始化:
-
数据类型 数组名[数组长度] = { 值1,值2,值3, ...... , 值n};
-
数据类型 数组名[ ] = { 值1,值2,值3, ...... , 值n};
-
数据类型 数组名[数组长度] = { 0 }; // 把全部的元素初始化为0。
-
数据类型 数组名[数组长度] = { }; // 把全部的元素初始化为0。
注意:如果{}内不足数组长度个数据,剩余数据用0补全,但是,不建议这么用,你可能在数组中漏了某个值。如果想把数组中全部的元素初始化为0,可以在{}内只填一个0或什么也不填。
C++11标准可以不写等于号。
5.清空数组
用memset()函数可以把数组中全部的元素清零。(只适用于C++基本数据类型)
函数原型:void *memset(void *s, int c, size_t n);
注意,在Linux下,使用memcpy()函数需要包含头文件#include <string.h>
6.复制数组
用memcpy()函数可以把数组中全部的元素复制到另一个相同大小的数组。(只适用于C++基本数据类型)
函数原型:void *memcpy(void *dest, const void *src, size_t n);
注意,在Linux下,使用memcpy()函数需要包含头文件#include <string.h>
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { int bh[] = {3, 6, 1,6,7,4,3,5,6,7,8,322,2,3,9}; // 超女编号。 string name[3]; // 超女姓名。 for (int ii = 0; ii < sizeof(bh)/sizeof(int); ii++) { cout << "bh["<<ii<<"]=" << bh[ii] << endl; } int bh1[sizeof(bh) / sizeof(int)]; // 数组长度必须是整数,可以是常量,也可以是变量和表达式。 memcpy(bh1, bh, sizeof(bh)); // 把数组bh中的内容复制到bh1。 for (int ii = 0; ii < sizeof(bh1) / sizeof(int); ii++) { cout << "bh1[" << ii << "]=" << bh1[ii] << endl; } }
四十八、一维数组和指针
1.指针的算术
将一个整型变量加1后,其值将增加1。
但是,将指针变量(地址的值)加1后,增加的量等于它指向的数据类型的字节数。
2.数组的地址
a.数组在内存中占用的空间是连续的。
b.C++将数组名解释为数组第0个元素的地址。
c.数组第0个元素的地址和数组首地址的取值是相同的。
d.数组第n个元素的地址是:数组首地址+n
e.C++编译器把 数组名[下标] 解释为 *(数组首地址+下标)
3).数组的本质
数组是占用连续空间的一块内存,数组名被解释为数组第0个元素的地址。C++操作这块内存有两种方法:数组解释法和指针表示法,它们是等价的。
4.数组名不一定会被解释为地址
在多数情况下,C++将数组名解释为数组的第0个元素的地址,但是,将sizeof运算符用于数据名时,将返回整个数组占用内存空间的字节数。
可以修改指针的值,但数组名是常量,不可修改。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { char a; cout << "sizeof(char)=" << sizeof(char) << endl; // 1字节 short b; cout << "sizeof(short)=" << sizeof(short) << endl; // 2字节 int c; cout << "sizeof(int)=" << sizeof(int) << endl; // 4字节 double d; cout << "sizeof(double)=" << sizeof(double) << endl; // 8字节 cout << "a的地址是:" << (void *)& a << endl; cout << "a的地址+1是:" << (void*)( & a + 1) << endl; cout << "b的地址是:" << (void*)&b << endl; cout << "b的地址+1是:" << (void*)(&b + 1) << endl; cout << "c的地址是:" << (void*)&c << endl; cout << "c的地址+1是:" << (void*)(&c + 1) << endl; cout << "d的地址是:" << (void*)&d << endl; cout << "d的地址+1是:" << (void*)(&d + 1) << endl; } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { double a[5]; cout << "a的值是:" << (long long) a << endl; cout << "&a的值是:" << (long long)&a << endl; cout << "a[0]的地址是:" << (long long) &a[0] << endl; cout << "a[1]的地址是:" << (long long) &a[1] << endl; cout << "a[2]的地址是:" << (long long) &a[2] << endl; cout << "a[3]的地址是:" << (long long) &a[3] << endl; cout << "a[4]的地址是:" << (long long) &a[4] << endl; double* p = a; cout << "p的值是:" << (long long)p << endl; cout << "p+0的值是:" << (long long)(p+ 0) << endl; cout << "p+1的值是:" << (long long)(p + 1) << endl; cout << "p+2的值是:" << (long long)(p + 2) << endl; cout << "p+3的值是:" << (long long)(p + 3) << endl; cout << "p+4的值是:" << (long long)(p + 4) << endl; } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { int a[5] = { 3 , 6 , 5 , 8 , 9 }; // 用数组表示法操作数组。 cout << "a[0]的值是:" << a[0] << endl; cout << "a[1]的值是:" << a[1] << endl; cout << "a[2]的值是:" << a[2] << endl; cout << "a[3]的值是:" << a[3] << endl; cout << "a[4]的值是:" << a[4] << endl; // 用指针表示法操作数组。 int* p = a; cout << "*(p+0)的值是:" << *(p+ 0) << endl; cout << "*(p+1)的值是:" << *(p + 1) << endl; cout << "*(p+2)的值是:" << *(p + 2) << endl; cout << "*(p+3)的值是:" << *(p + 3) << endl; cout << "*(p+4)的值是:" << *(p + 4) << endl; }
四十九、一维数组用于函数的参数
1.指针的数组表示
-
在C++内部,用指针来处理数组。
-
C++编译器把 数组名[下标] 解释为 *(数组首地址+下标)
-
C++编译器把 地址[下标] 解释为 *(地址+下标)
2.一维数组用于函数的参数
一维数组用于函数的参数时,只能传数组的地址,并且必须把数组长度也传进去,除非数组中有最后一个元素的标志。
书写方法有两种:
void func(int* arr, int len); void func(int arr[], int len);
注意:
在函数中,可以用数组表示法,也可以用指针表示法。
在函数中,不要对指针名用sizeof运算符,它不是数组名。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { char a[20]; // 这是一个长度为20的字符型数组。 int* p = (int *)a; // 让整型指针p指向数组a的内存。 for (int ii = 0; ii < 6; ii++) { p[ii] = ii + 300; // 用数组表示法操作指针。 } for (int ii = 0; ii < 6; ii++) { cout << "*(p+" << ii << ")的值是:" << *(p + ii) << endl; // 地址[下标] 解释为 *(地址+下标)。 } } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // void func(int *arr,int len) void func(int arr[],int len) { for (int ii = 0; ii < len; ii++) { cout << "arr[" << ii << "]的值是:" << arr[ii] << endl; // 用数组表示法操作指针。 cout << "*(arr+" << ii << ")的值是:" << *(arr + ii) << endl; // 地址[下标] 解释为 *(地址+下标)。 } } int main() { int a[] = {2,8,4,6,7,1,9}; func(a, sizeof(a) / sizeof(int)); }
五十、用new动态创建一维数组
-
普通数组在栈上分配内存,栈很小;如果需要存放更多的元素,必须在堆上分配内存。
-
动态创建一维数组的语法:数据类型 *指针=new 数据类型[数组长度];
-
释放一维数组的语法:delete [] 指针;
注意:
- 动态创建的数组没有数组名,不能用sizeof运算符。
- 可以用数组表示法和指针表示法两种方式使用动态创建的数组。
- 必须使用delete[]来释放动态数组的内存(不能只用delete)。
- 不要用delete[]来释放不是new[]分配的内存。
- 不要用delete[]释放同一个内存块两次(否则等同于操作野指针)。
- 对空指针用delete[]是安全的(释放内存后,应该把指针置空nullptr)。
- 声明普通数组的时候,数组长度可以用变量,相当于在栈上动态创建数组,并且不需要释放。
- 如果内存不足,调用new会产生异常,导致程序中止;如果在new关键字后面加(std::nothrow)选项,则返回nullptr,不会产生异常。
- 为什么用delete[]释放数组的时候,不需要指定数组的大小?因为系统会自动跟踪已分配数组的内存。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { int *arr=new int[8]; // 创建8个元素的整型数组。 for (int ii = 0; ii < 8; ii++) { arr[ii] = 100 + ii; // 数组表示法。 cout << "arr[" << ii << "]=" << *(arr + ii) << endl; // 指针表示法。 } delete[]arr; }
五十一、一维数组的排序qsort
qsort()函数用于对各种数据类型的数组进行排序。
函数的原型:
void qsort(void base, size_t nmemb, size_t size, int (compar)(const void *, const void *));
第一个参数:数组的起始地址。
第二个参数:数组元素的个数(数组长度)。
第三个参数:数组元素的大小(sizeof(数组的数据类型))。
第四个参数:回调函数的地址。
回调函数决定了排序的顺序,声明如下:
int compar(const void *p1, const void *p2);
1.如果函数的返回值< 0 ,那么p1所指向元素会被排在p2所指向元素的前面。
2.如果函数的返回值==0,那么p1所指向元素与p2所指向元素的顺序不确定。
3.如果函数的返回值> 0 ,那么p1所指向元素会被排在p2所指向元素的后面。
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
qsort()函数的其它细节:
-
形参中的地址用void是为了支持任意数据类型,在回调函数中必须具体化。
-
为什么需要第三个形参size_t size?
-
size_t是C标准库中定义的,在64位系统中是8字节无符号整型(unsigned long long)。
-
typedef unsigned long long size_t
-
排序的需求除了升序和降序,还有很多不可预知的情况,只能用回调函数。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int compasc(const void* p1, const void* p2) // 升序的回调函数。 { return *((int*)p1) - *((int*)p2); } int compdesc(const void* p1, const void* p2) // 降序的回调函数。 { return *((int*)p2) - *((int*)p1); } int main() { int a[8] = { 4,2,7,5,8,6,1,3 }; qsort(a,sizeof(a)/sizeof(int),sizeof(int),compasc); // 对数组a进行升序排序。 for (int ii = 0; ii < 8; ii++) { cout << "a[" << ii << "]=" << a[ii] << endl; } qsort(a, sizeof(a) / sizeof(int), sizeof(int), compdesc); // 对数组a进行降序排序。 for (int ii = 0; ii < 8; ii++) { cout << "a[" << ii << "]=" << a[ii] << endl; } }
五十二、一维数组的查找-折半查找
折半查找也叫二分查找,只适用于已排序的数组(升序降序都可以)。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // 在arr中查找key,成功返回key在arr中的数组下标,失败返回-1。 int search(int arr[], int len, int key) { int low = 0, high = len-1,mid; // 初始化:low=0,high=数组长度-1。 while (low <= high) { mid = (low + high) / 2; // 计算mid指针的位置。 if (arr[mid] == key) return mid; // 查找成功。 else if (arr[mid] > key) high = mid - 1; // 继续在前半区查找。 else low = mid + 1; // 继续在后半区查找。 } return -1; // 查找失败。 } int main() { int a[10] = { 7,9,12,16,21,25,30,35,41,48 }; // 必须是已排好序的数组。 if (search(a, 10, 30) >= 0) cout << "在数组a中查找30成功。\n"; else cout << "在数组a中查找30失败。\n"; }
五十三、C风格的字符串
-
C语言约定:如果字符型(char)数组的末尾包含了空字符\0(也就是0),那么该数组中的内容就是一个字符串。
-
因为字符串需要用0结尾,所以在声明字符数组的时候,要预留多一个字节用来存放0。
-
char name[21]; // 声明一个最多存放20个英文字符或十个中文的字符串。
1.初始化方法
char name[11]; // 可以存放10个字符,没有初始化,里面是垃圾值。 char name[11] = "hello"; // 初始内容为hello,系统会自动添加0。 char name[] = { "hello" }; // 初始内容为hello,系统会自动添加0,数组长度是6。 char name[11] = { "hello" }; // 初始内容为hello,系统会自动添加0。 char name[11] { "hello" }; // 初始内容为hello,系统会自动添加0。C++11标准。 char name[11] = { 0 }; // 把全部的元素初始化为0。
2.清空字符串
memset(name,0,sizeof(name)); // 把全部的元素置为0。 name[0]=0; // 不规范,有隐患,不推荐。
3.字符串复制或赋值strcpy()
-
char strcpy(char dest, const char* src);
-
功能: 将参数src字符串拷贝至参数dest所指的地址。
-
返回值: 返回参数dest的字符串起始地址。
-
复制完字符串后,会在dest后追加0。
-
如果参数dest所指的内存空间不够大,会导致数组的越界。
4.字符串复制或赋值strncpy()
-
char * strncpy(char* dest,const char* src, const size_t n);
-
功能:把src前n个字符的内容复制到dest中。
-
返回值:dest字符串起始地址。
-
如果src字符串长度小于n,则拷贝完字符串后,在dest后追加0,直到n个。
-
如果src的长度大于等于n,就截取src的前n个字符,不会在dest后追加0。
-
如果参数dest所指的内存空间不够大,会导致数组的越界。
5.获取字符串的长度strlen()
-
size_t strlen( const char* str);
-
功能:计算字符串的有效长度,不包含0。
-
返回值:返回字符串的字符数。
-
strlen()函数计算的是字符串的实际长度,遇到0结束。
6.字符串拼接strcat()
-
char strcat(char dest,const char* src);
-
功能:将src字符串拼接到dest所指的字符串尾部。
-
返回值:返回dest字符串起始地址。
-
dest最后原有的结尾字符0会被覆盖掉,并在连接后的字符串的尾部再增加一个0。
-
如果参数dest所指的内存空间不够大,会导致数组的越界。
7.字符串拼接strncat()
-
char strncat (char dest,const char* src, const size_t n);
-
功能:将src字符串的前n个字符拼接到dest所指的字符串尾部。
-
返回值:返回dest字符串的起始地址。
-
如果n大于等于字符串src的长度,那么将src全部追加到dest的尾部,如果n小于字符串src的长度,只追加src的前n个字符。
-
strncat会将dest字符串最后的0覆盖掉,字符追加完成后,再追加0。
-
如果参数dest所指的内存空间不够大,会导致数组的越界。
8.字符串比较strcmp()和strncmp()
-
int strcmp(const char *str1, const char *str2 );
-
功能:比较str1和str2的大小。
-
返回值:相等返回0,str1大于str2返回1,str1小于str2返回-1;
-
int strncmp(const char *str1,const char *str2 ,const size_t n);
-
功能:比较str1和str2前n个字符的大小。
-
返回值:相等返回0,str1大于str2返回1,str1小于str2返回-1;
-
两个字符串比较的方法是比较字符的ASCII码的大小,从两个字符串的第一个字符开始,如果分不出大小,就比较第二个字符,如果全部的字符都分不出大小,就返回0,表示两个字符串相等。
-
在实际开发中,程序员一般只关心字符串是否相等,不关心哪个字符串更大或更小。
9.查找字符strchr()和strrchr()
-
const char *strchr(const char *s, int c);
-
返回在字符串s中第一次出现c的位置,如果找不到,返回0。
-
const char *strrchr(const char *s, int c);
-
返回在字符串s中最后一次出现c的位置,如果找不到,返回0。
10.查找字符串strstr()
-
char strstr(const char str,const char* substr);
-
功能:检索子串在字符串中首次出现的位置。
-
返回值:返回字符串str中第一次出现子串substr的地址;如果没有检索到子串,则返回0。
11.用于string的表达式
可以把C风格的字符串用于包含了string类型的赋值拼接等表达式中。
12.注意事项
a.字符串的结尾标志是0,按照约定,在处理字符串的时候,会从起始位置开始搜索0,直找下去,找到为止(不会判断数组是否越界)。
b.结尾标志0后面的都是垃圾内容。
c.字符串在每次使用前都要初始化,减少入坑的可能,是每次,不是第一次。
d.不要在子函数中对字符指针用sizeof运算,所以,不能在子函数中对传入的字符串进行初始化,除非字符串的长度也作为参数传入到了子函数中。
e.在VS中,如果要使用C标准的字符串操作函数,要在源代码文件的最上面加
#define _CRT_SECURE_NO_WARNINGS 示例 #define _CRT_SECURE_NO_WARNINGS #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { char name[11]; memset(name, 0, sizeof(name)); // name[0] = 0; // strcpy(name, "hello"); strncpy(name, "hello", 3); cout << "name=" << name << endl; cout << "name[0]=" << (int)name[0] << endl; cout << "name[1]=" << (int)name[1] << endl; cout << "name[2]=" << (int)name[2] << endl; cout << "name[3]=" << (int)name[3] << endl; cout << "name[4]=" << (int)name[4] << endl; cout << "name[5]=" << (int)name[5] << endl; cout << "name[6]=" << (int)name[6] << endl; cout << "name[7]=" << (int)name[7] << endl; cout << "name[8]=" << (int)name[8] << endl; cout << "name[9]=" << (int)name[9] << endl; cout << "name[10]=" << (int)name[10] << endl; }
五十四、二维数组
一维数组的数学概念是线性表,二维数组的数学概念是矩阵。
1.创建二维数组
声明二维数组的语法:数据类型 数组名[行数][列数];
注意:数组长度必须是整数,可以是常量,也可以是变量和表达式。
C90规定必须用常量表达式指明数组的大小,C99允许使用整型非常量表达式。经测试,在VS中可以用用整型非常量表达式,不能用变量;但是,Linux中还可以用变量。
2.二维数组的使用
可以通过行下标和列下标访问二维数组中元素,下标从0开始。
二维数组中每个元素的特征和使用方法与单个变量完全相同。
语法:数组名[行下标][列下标]
注意:
- 二维数组下标也必须是整数,可以是常量,也可以是变量。
- 合法的行下标取值是:0~(行数-1)。
- 合法的列下标取值是:0~(列数-1)。
3.二维数组占用内存的情况
用sizeof(数组名)可以得到整个二维数组占用内存空间的大小(只适用于C++基本数据类型)。
二维数组在内存中占用的空间是连续的。
4.二维数组的初始化
声明的时候初始化:
数据类型 数组名[行数][列数] = { {数据1,数据2 } ,{数据3,数据4 },...... }; 数据类型 数组名[行数][列数] = { 数据1,数据2,数据3,数据4, ......}; 数据类型 数组名[ ][列数] = { 数据1,数据2,数据3,数据4,......}; 数据类型 数组名[行数][列数] = { 0 }; // 把全部的元素初始化为0。 数据类型 数组名[行数][列数] = { }; // 把全部的元素初始化为0。
注意:如果{}内不足数组长度个数据,剩余数据用0补全,但是,不建议这么用,你可能在数组中漏了某个值。如果想把数组中全部的元素初始化为0,可以在{}内只填一个0或什么也不填。
C++11标准可以不写等于号。
5.清空二维数组
用memset()函数可以把二维数组中全部的元素清零。(只适用于C++基本数据类型)
函数原型:void *memset(void *s, int c, size_t n);
注意,在Linux下,使用memcpy()函数需要包含头文件#include <string.h>
6.复制二维数组
用memcpy()函数可以把二维数组中全部的元素复制到另一个相同大小的数组(没说多少维)。(只适用于C++基本数据类型)
函数原型:void *memcpy(void *dest, const void *src, size_t n);
注意,在Linux下,使用memcpy()函数需要包含头文件#include <string.h>
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // int bh[2][3] = { {11,12,13},{21,22,23} }; // 声明一个两行三列的二维数组,存放超女的编号。 // int bh[2][3] = { 11,12,13,21,22,23 }; int bh[][3] = { 11,12,13,21,22,23 }; /*bh[0][0] = 11; bh[0][1] = 12; bh[0][2] = 13; bh[1][0] = 21; bh[1][1] = 22; bh[1][2] = 23;*/ /*cout << "bh[0][0] = " << bh[0][0] << " bh[0][1] = " << bh[0][1] << " bh[0][2] = " << bh[0][2] << endl; cout << "bh[1][0] = " << bh[1][0] << " bh[1][1] = " << bh[1][1] << " bh[1][2] = " << bh[1][2] << endl;*/ for (int ii = 0; ii < 2; ii++) // 第一层循环表示行数,循环继续的条件是计数器小于行数。 { for (int jj = 0; jj < 3; jj++) // 第二层循环表示列数,循环继续的条件是计数器小于列数。 { cout << "&bh["<<ii<<"]["<<jj<<"] = " << (long long) & bh[ii][jj] << " "; // 处理二维数组的每个元素。 } cout << endl; // 每处理一行数据后,输出一个换行。 } int* p = (int *)bh; for (int ii = 0; ii < 6; ii++) { cout << "p[" << ii << "]=" << p[ii] << endl; // 一维数组的数组表示法。 } }
五十五、二维数组用于函数的参数
int* p; // 整型指针。 int* p[3]; // 一维整型指针数组,元素是3个整型指针(p[0]、p[1]、p[2])。 int* p(); // 函数p的返回值类型是整型的地址。 int (*p)(int ,int); // p是函数指针,函数的返回值是整型。
1.行指针(数组指针)
声明行指针的语法:数据类型 (*行指针名)[行的大小]; // 行的大小即数组长度。 int (*p1)[3]; // p1是行指针,用于指向数组长度为3的int型数组。 int (*p2)[5]; // p2行指针,用于指向数组长度为5的int型数组。 double (*p3)[5]; // p3是行指针,用于指向数组长度为5的double型数组。
一维数组名被解释为数组第0个元素的地址。
对一维数组名取地址得到的是数组的地址,是行地址。
2.二维数组名是行地址
int bh[2][3] = { {11,12,13},{21,22,23} };
-
bh是二维数组名,该数组有2两元素,每一个元素本身又是一个数组长度为3的整型数组。
-
bh被解释为数组长度为3的整型数组类型的行地址。
-
如果存放bh的值,要用数组长度为3的整型数组类型的行指针。
int (*p)[3]=bh; int bh[4][2][3];
-
bh是三维数组名,该数组有4元素,每一个元素本身又是一个2行3列的二维数组。
-
bh被解释为2行3列的二维数组类型的二维地址。
-
如果存放bh的值,要用2行3列的二维数组类型的二维指针。
int (*p)[2][3]=bh;
3.把二维数组传递给函数
如果要把bh传给函数,函数的声明如下:
void func(int (*p)[3],int len); void func(int p[][3],int len);
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { int a[10]; cout << "数组a第0个元素的地址:" << a<< endl; cout << "数组a的地址:" << &a << endl; cout << "数组a第0个元素的地址+1:" << a + 1 << endl; // 地址的增加量是4。 cout << "数组a的地址+1:" << &a + 1 << endl; // 地址的增加量是40。 } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // void func(int(*p)[3], int len) void func(int p[][3], int len) { for (int ii = 0; ii < len; ii++) { for (int jj = 0; jj < 3; jj++) cout << "p[" << ii << "][" << jj << "]=" << p[ii][jj] << " " ; cout << endl; } } int main() { int bh[2][3] = { {11,12,13},{21,22,23} }; func(bh,2); } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void func(int (*p)[2][3]) { int ii = 1; // 遍历三维数组p,给它的每个元素赋值。 for (int a = 0; a < 4; a++) for (int b = 0; b < 2; b++) for (int c = 0; c < 3; c++) p[a][b][c] = ii++; } int main() { int bh[4][2][3]; // 假设有4个超女方阵,每个方阵有2行,每行有3个超女。 memset(bh, 0, sizeof(bh)); func(bh); for (int a = 0; a < 4; a++) { for (int b = 0; b < 2; b++) { for (int c = 0; c < 3; c++) cout << bh[a][b][c] <<"\t"; cout << endl; // 每显示一行后,输出一个换行符。 } cout << endl<<endl; // 每显示一个方阵后,输出两个换行符。 } }
五十六、结构体的基本概念
结构体是用户自定义的类型,可以将多种数据的表示合并到一起,描述一个完整的对象。
使用结构体有两个步骤:1。定义结构体描述(类型);2。创建结构体变量。
1.定义结构体描述
定义结构体描述的语法:
struct 结构体名 { 成员一的数据类型 成员名一; 成员二的数据类型 成员名二; 成员三的数据类型 成员名三; ...... 成员n的数据类型 成员名n; };
注意:
-
结构体名是标识符。
-
结构体的成员可以是任意数据类型。
-
定义结构体描述的代码可以放在程序的任何地方,一般放在main函数的上面或头文件中。
-
结构体成员可以用C++的类(如string),但是不提倡。
-
在C++中,结构体中可以有函数,但是不提倡。
-
在C++11中,定义结构体的时候可以指定缺省值。
2.创建结构体变量
创建结构体变量的语法: -
struct 结构体名 结构体变量名;
-
也可以为结构体成员赋初始值。
-
struct 结构体名 结构体变量名={成员一的值, 成员二的值,......, 成员n的值};
-
C++11可以不写等于号。
-
如果大括号内未包含任何东西或只写一个0,全部的成员都将被设置为0。
-
struct 结构体名 结构体变量名={0};
注意:
在C++中,struct关键字可以不写。
可以在定义结构体的时候创建结构体变量。
3.使用结构体
在C++程序中,用成员运算符(.)来访问结构体的每个成员。结构体中的每个成员具备普通变量的全部特征。
语法:结构体变量名.结构体成员名;
4.占用内存的大小
用sizeof运算符可以得到整个结构体占用内存的大小。
注意:整个结构体占用内存的大小不一定等于全部成员占用内存之和。
内存对齐:#pragma pack(字节数)
合理使用内存对齐规则,某些节省内存的做法可能毫无意义。
5.清空结构体
创建的结构体变量如果没有初始化,成员中有垃圾值。
用memset()函数可以把结构体中全部的成员清零。(只适用于C++基本数据类型)
bzero()函数也可以。
6.复制结构体
用memcpy()函数把结构体中全部的元素复制到另一个相同类型的结构体(只适用于C++基本数据类型)。
也可以直接用等于号(只适用于C++基本数据类型)。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 #pragma pack(8) // 超女基本信息结构体st_girl,存放了超女全部的数据项。 struct st_girl { char name[21]; // 姓名。 int age; // 年龄。 double weight; // 体重(kg)。 char sex; // 性别:X-女;Y-男。 bool yz; // 颜值:true-漂亮;false-不漂亮。 }; int main() { st_girl stgirl{"西施",26,33.8,'X',true}; // 创建结构体变量。 cout << "sizeof(st_girl)=" << sizeof(st_girl) << endl; memset(&stgirl, 0, sizeof(stgirl)); cout << "姓名:" << stgirl.name << ",年龄:" << stgirl.age << ",体重:" << stgirl.weight << ",性别:" << stgirl.sex << ",颜值:" << stgirl.yz << endl; }
五十七、结构体指针
结构体是一种自定义的数据类型,用结构体可以创建结构体变量。
1.基本语法
在C++中,用不同类型的指针存放不同类型变量的地址,这一规则也适用于结构体。如下:
struct st_girl girl; // 声明结构体变量girl。 struct st_girl *pst=&girl; // 声明结构体指针,指向结构体变量girls。
通过结构体指针访问结构体成员,有两种方法:
(*指针名).成员变量名 // (*pst).name和(*pst).age 或者: 指针名->成员变量名 // pst->name和*pst->age
在第一种方法中,圆点.的优先级高于,(指针名)两边的括号不能少。如果去掉括号写成(指针名).成员变量名,那么相当于(指针名.成员变量名),意义就完全不一样了。
在第二种方法中,->是一个新的运算符。
上面的两种方法是等效的,程序员通常采用第二种方法,更直观。
注意:与数组不一样,结构体变量名没有被解释为地址。
2.用于函数的参数
如果要把结构体传递给函数,实参取结构体变量的地址,函数的形参用结构体指针。
如果不希望在函数中修改结构体变量的值,可以对形参加const约束。
3.用于动态分配内存
用结构体指针指向动态分配的内存的地址。
示例:
#define _CRT_SECURE_NO_WARNINGS // 如果要使用C标准库的字符串函数,需要加上这一行代码 #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 struct st_girl { char name[21]; // 姓名。 int age; // 年龄。 double weight; // 体重(kg)。 char sex; // 性别:X-女;Y-男。 bool yz; // 颜值:true-漂亮;false-不漂亮。 }; void func(const st_girl* pst) { cout << "姓名:" << pst->name << ",年龄:" << pst->age << ",体重:" << pst->weight << ",性别:" << pst->sex << ",颜值:" << pst->yz << endl; } int main() { // st_girl stgirl={"西施",26,33.8,'X',true}; // 创建结构体变量。 st_girl* stgirl = new st_girl({ "西施",26,33.8,'X',true }); // memset(stgirl, 0, sizeof(st_girl)); cout << "姓名:" << stgirl->name << ",年龄:" << stgirl->age << ",体重:" << stgirl->weight << ",性别:" << stgirl->sex << ",颜值:" << stgirl->yz << endl; func(stgirl); cout << "姓名:" << stgirl->name << ",年龄:" << stgirl->age << ",体重:" << stgirl->weight << ",性别:" << stgirl->sex << ",颜值:" << stgirl->yz << endl; delete stgirl; }
五十八、结构体数组
-
结构体可以被定义成数组变量,本质上与其它类型的数组变量没有区别。
-
声明结构体数组的语法:struct 结构体类型 数组名[数组长度];
-
初始化结构体数组,要结合使用初始化数组的规则和初始化结构体的规则。
struct st_girl girls[2]={{"西施",26,43.8,'X',true},{"西瓜",25,52.8,'X',false}};
使用结构体数组可以用数组表示法,也可以用指针表示法。
示例:
#define _CRT_SECURE_NO_WARNINGS // C标准的字符串操作函数需要加这一行。 #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // 超女基本信息结构体st_girl,存放了超女全部的数据项。 struct st_girl { char name[21]; // 姓名。 int age; // 年龄。 double weight; // 体重(kg)。 char sex; // 性别:X-女;Y-男。 bool yz; // 颜值:true-漂亮;false-不漂亮。 }; int main() { // st_girl stgirl={"西施",26,33.8,'X',true}; // 创建结构体变量。 st_girl girls[3]; memset(girls, 0, sizeof(girls)); // 清空整个数组。 strcpy((girls+0)->name, "西施"); (girls+0)->age = 25; girls[0].weight = 45; girls[0].sex = 'X'; girls[0].yz = true; girls[1] = { "西瓜",2,10.6,'Y',false }; // C++11标准的语法。 *(girls+2) = { "冰冰",23,50.3,'X',true }; for (int ii = 0; ii < 3; ii++) { cout << "姓名:" << (girls+ii)->name << ",年龄:" << (girls + ii)->age << ",体重:" << girls[ii].weight << ",性别:" << girls[ii].sex << "颜值:" << girls[ii].yz << endl; } }
五十九、结构体嵌入数组和结构体
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 // 超女基本信息结构体st_girl,存放了超女全部的数据项。 struct st_girl { char name[21]; // 姓名。 int score[2][3] = {11,12,13,21,22,23}; // 存放评委的打分。 int age; // 年龄。 double weight; // 体重(kg)。 char sex; // 性别:X-女;Y-男。 bool yz; // 颜值:true-漂亮;false-不漂亮。 }; void func(st_girl* pst) { for (int ii = 0; ii < 2; ii++) for (int jj = 0; jj < 3; jj++) cout << "pst->score[" << ii << "][" << jj << "]=" << pst->score[ii][jj] << endl; } int main() { st_girl girl; func(&girl); /*for (int ii = 0; ii < 3; ii++) girl.score[ii] = 100 + ii; for (int ii = 0; ii < 3; ii++) cout <<"girl.score["<<ii<<"] =" << girl.score[ii]<<endl;*/ } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 struct st_pet // 宠物结构体。 { char name[21]; // 宠物的名字。 char type[21]; // 宠物的物种。 }; struct st_girl // 超女基本信息结构体st_girl,存放了超女全部的数据项。 { char name[21]; // 姓名。 int age; // 年龄。 double weight; // 体重(kg)。 char sex; // 性别:X-女;Y-男。 bool yz; // 颜值:true-漂亮;false-不漂亮。 struct st_pet pet; // 宠物。 }; int main() { st_girl girl = { "西施",23,50.5,'X',true,{"宝宝","鸭子"} }; cout << "姓名:" << girl.name << "的宠物是一只" << girl.pet.type << ",名字叫" << girl.pet.name << "。" << endl; girl = { "西瓜",25,51.5,'X',true,{"贝贝","天鹅"} }; // C++11标准的语法。 cout << "姓名:" << girl.name << "的宠物是一只" << girl.pet.type << ",名字叫" << girl.pet.name << "。" << endl; girl.pet = {"小白","狗"}; // C++11标准的语法。 cout << "姓名:" << girl.name << "的宠物是一只" << girl.pet.type << ",名字叫" << girl.pet.name << "。" << endl; } 74、结构体中的指针 如果结构体中的指针指向的是动态分配的内存地址: 对结构体用sizeof运算可能没有意义。 对结构体用memset()函数可能会造成内存泄露。 C++的字符串string中有一个指针,指向了动态分配内存的地址。 struct string { char *ptr; // 指向动态分配内存的地址。 ...... }
示例:
struct st_message // QQ报文/消息结构体 { int srcqq; // 发送方的QQ号码。 int dstqq; // 接收方的QQ号码。 char context[1024]; // 消息内容。 }; int main() { st_message message; // 创建QQ报文/消息结构体变量。 while (true) { memset(message, 0, sizeof(message)); // 清空QQ报文/消息结构体。 // 接收QQ报文/消息。 // 处理QQ报文/消息。 // 把QQ报文/消息发送给接收方。 } // 其它代码。 } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 struct st_t { int a; int *p; }; int main() { st_t stt; // 创建结构体变量。 memset(&stt, 0, sizeof(st_t)); stt.a = 3; stt.p = new int[100]; // 动态创建一个长度为100的整型数组,让指针stt.p指向数组的地址。 cout << "sizeof(stt)=" << sizeof(stt) << endl; cout << "调用前:stt.a=" << stt.a << ",stt.p=" << stt.p << endl; // memset(&stt, 0, sizeof(st_t)); stt.a = 0; // 清空成员a。 memset(stt.p, 0, 100 * sizeof(int)); // 清空成员p指向的内存中的内容。 cout << "调用后:stt.a=" << stt.a << ",stt.p=" << stt.p << endl; delete [] stt.p; // 释放动态分配的内存。 } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 struct st_girl { string name; // 超女姓名。 }; int main() { st_girl girl; // 创建结构体变量。 girl.name = "西施"; cout << "girl.name=" << girl.name << endl; // memset(&girl, 0, sizeof(girl)); girl.name = "好大的西瓜"; cout << "girl.name=" << girl.name << endl; }
六十、共同体
共同体(共用体、联合体)是一种数据格式,它能存储不同的数据类型,但是,在同一时间只能存储其中的一种类型。
声明共同体的语法:
union 共同体名 { 成员一的数据类型 成员名一; 成员二的数据类型 成员名二; 成员三的数据类型 成员名三; ...... 成员n的数据类型 成员名n; };
注意:
- 共同体占用内存的大小是它最大的成员占用内存的大小(内存对齐)。
- 全部的成员使用同一块内存。
- 共同体中的值为最后被赋值的那个成员的值。
- 匿名共同体没有名字,可以在定义的时候创建匿名共同体变量(VS和Linux有差别),也可以嵌入结构体中。
应用场景:
当数据项使用两种或更多种格式(但不会同时使用)时,可节省空间(嵌入式系统)。
用于回调函数的参数(相当于支持多种数据类型)。
示例一:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 union // 声明共同体udata。 { int a; double b; char c[25]; } data; int main() { // udata data; // 定义共同体变量。 cout << "sizeof(data)=" << sizeof(data) << endl; cout << "data.a的地址是:" << (void*)&data.a << endl; cout << "data.b的地址是:" << (void*)&data.b << endl; cout << "data.c的地址是:" << (void*)&data.c << endl; data.a = 3; data.b = 8.8; strcpy(data.c, "我是一只傻傻鸟。"); cout << "data.a=" << data.a << endl; cout << "data.b=" << data.b << endl; cout << "data.c=" << data.c << endl; }
示例二:
#define _CRT_SECURE_NO_WARNINGS #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 struct st_girl // 声明超女结构体。 { int no; // 超女编号。 union // 声明匿名共同体。 { int a; double b; char c[21]; }; }; int main() { struct st_girl girl; cout << "girl.a的地址是:" << (void*) &girl.a << endl; cout << "girl.b的地址是:" << (void*) &girl.b << endl; cout << "girl.c的地址是:" << (void*) &girl.c << endl; girl.a = 3; girl.b = 8.8; strcpy(girl.c, "我是一只傻傻鸟。"); cout << "girl.a=" << girl.a << endl; cout << "girl.b=" << girl.b << endl; cout << "girl.c=" << girl.c << endl; }
六十一、枚举
枚举是一种创建符号常量的方法。
枚举的语法:
enum 枚举名 { 枚举量1 , 枚举量2 , 枚举量3, ......, 枚举量n };
例如:
-
enum colors { red , yellow , blue };
-
这条语句完成了两项工作:
-
让colors成了一种新的枚举类型的名称,可以用它创建枚举变量。
-
将red、yellow、blue作为符号常量,默认值是整数的0、1、2。
-
注意:
-
用枚举创建的变量取值只能在枚举量范围之内。
-
枚举的作用域与变量的作用域相同。
-
可以显式的设置枚举量的值(必须是整数)。
-
enum colors {red=1,yellow=2,blue=3};
-
可以只显式的指定某些枚举量的值(枚举量的值可以重复)。
-
enum colors {red=10,yellow,blue};
-
可以将整数强制转换成枚举量,语法:枚举类型(整数)
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { enum colors { red=0, yellow=1, blue=2, other=3 }; // 创建枚举类型colors。 colors cc = yellow; // 创建枚举变量,并赋初始值。 //colors cc = colors(1); // 创建枚举变量,并赋初始值。 cout << "red=" << red << ",yellow=" << yellow << ",blue=" << blue << ",other=" << other << endl; switch (cc) { case red: cout << "红色。\n"; break; case yellow: cout << "黄色。\n"; break; case blue: cout << "蓝色。\n"; break; default: cout << "未知。\n"; } }
六十二、引用的基本概念
-
引用变量是C++新增的复合类型。
-
引用是已定义的变量的别名。
-
引用的主要用途是用作函数的形参和返回值。
-
声明/创建引用的语法:数据类型 &引用名=原变量名;
注意:
- 引用的数据类型要与原变量名的数据类型相同。
- 引用名和原变量名可以互换,它们值和内存单元是相同的。
- 必须在声明引用的时候初始化,初始化后不可改变。
- C和C++用&符号来指示/取变量的地址,C++给&符号赋予了另一种含义。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 声明 / 创建引用的语法:数据类型 & 引用名 = 原变量名; int a = 3; // 声明普通的整型变量。 int& ra = a; // 创建引用ra,ra是a的别名。 cout << " a的地址是:" << &a << ", a的值是:" << a << endl; cout << "ra的地址是:" << &ra << ",ra的值是:" << ra << endl; ra = 5; cout << " a的地址是:" << &a << ", a的值是:" << a << endl; cout << "ra的地址是:" << &ra << ",ra的值是:" << ra << endl; }
六十三、引用的本质
-
引用是指针常量的伪装。
-
引用是编译器提供的一个有用且安全的工具,去除了指针的一些缺点,禁止了部分不安全的操作。
-
变量是什么?变量就是一个在程序执行过程中可以改变的量。
-
换一个角度,变量是一块内存区域的名字,它代表了这块内存区域,当我们对变量进行修改的时候,会引起内存区域中内容的改变。
-
在计算机看来,内存区域根本就不存在什么名字,它仅有的标志就是它的地址,因此我们若想修改一块内存区域的内容,只有知道他的地址才能实现。
-
所谓的变量只不过是编译器给我们进行的一种抽象,让我们不必去了解更多的细节,降低我们的思维跨度而已。
-
程序员拥有引用,但编译器仅拥有指针(地址)。
-
引用的底层机制实际上是和指针一样的。不要相信有别名,不要认为引用可以节省一个指针的空间,因为这一切不会发生,编译器还是会把引用解释为指针。
-
引用和指针本质上没有区别。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 int main() { // 声明 / 创建引用的语法:数据类型 & 引用名 = 原变量名; // 语法:数据类型 * const 变量名; int a = 3; // 声明普通的整型变量。 int& ra = a; // 创建引用ra,ra是a的别名。 把int&替换成int* const 把a替换成&a int* const rb = &a; // 声明指针常量rb,让它指向变量a。 cout << " a的地址是:" << &a << ", a的值是:" << a << endl; cout << "ra的地址是:" << &ra << ", ra的值是:" << ra << endl; // 把&ra替换成ra,把ra替换成*ra cout << "rb的值是 :" << rb << ",*rb的值是:" << *rb << endl; ra = 5; cout << " a的地址是:" << &a << ", a的值是:" << a << endl; cout << "ra的地址是:" << &ra << ", ra的值是:" << ra << endl; cout << "rb的值是 :" << rb << ",*rb的值是:" << *rb << endl; }
六十四、引用用于函数的参数
-
把函数的形参声明为引用,调用函数的时候,形参将成为实参的别名。
-
这种方法也叫按引用传递或传引用。(传值、传地址、传引用只是说法不同,其实都是传值。)
-
引用的本质是指针,传递的是变量的地址,在函数中,修改形参会影响实参。
1.传引用的代码更简洁。
2.传引用不必使用二级指针。
3.引用的属性和特别之处。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void func1(int no, string str) // 传值。 { no = 8; str = "我有一只小小鸟。"; cout << "亲爱的" << no << "号:" << str << endl; } void func2(int* no, string* str) // 传地址。 { *no = 8; *str = "我有一只小小鸟。"; cout << "亲爱的" << *no << "号:" << *str << endl; } void func3(int &no, string &str) // 传引用。 { no = 8; str = "我有一只小小鸟。"; cout << "亲爱的" << no << "号:" << str << endl; } int main() { int bh = 3; // 超女的编号。 string message = "我是一只傻傻鸟。"; // 向超女表白的内容。 //func1(bh, message); // 传值。 //func2(&bh, &message); // 传地址。 func3(bh, message); // 传引用。 cout << "亲爱的" << bh << "号:" << message << endl; } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 struct st_girl // 定义超女结构体。 { int no; // 超女编号。 string str; // 表白内容。 }; void func1(st_girl girl) // 传值。 { girl.no = 8; girl.str = "我有一只小小鸟。"; cout << "亲爱的" << girl.no << "号:" << girl.str << endl; } void func2(st_girl *girl) // 传地址。 { girl->no = 8; girl->str = "我有一只小小鸟。"; cout << "亲爱的" << girl->no << "号:" << girl->str << endl; } void func3(st_girl & girl) // 传引用。 { girl.no = 8; girl.str = "我有一只小小鸟。"; cout << "亲爱的" << girl.no << "号:" << girl.str << endl; } int main() { st_girl girl = { 3,"我是一只傻傻鸟。" }; func1(girl); // 传值。 //func2(&girl); // 传地址。 //func3(girl); // 传引用。 cout << "亲爱的" << girl.no << "号:" << girl.str << endl; } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void func1(int** p) // 传地址,实参是指针的地址,形参是二级指针。 { *p = new int(3); // p是二级指针,存放指针的地址。 cout << "func1内存的地址是:" << *p << ",内存中的值是:" << **p << endl; } void func2(int*& p) // 传引用,实参是指针,形参是指针的别名。 { p = new int(3); // p是指针的别名。 cout << "func2内存的地址是:" << p << ",内存中的值是:" << *p << endl; } int main() { int* p = nullptr; // 存放在子函数中动态分配内存的地址。 func1(&p); // 传地址,实参填指针p的地址。 //func2(p); // 传引用,实参填指针p。 cout << "main 内存的地址是:" << p << ",内存中的值是:" << *p << endl; delete p; }
六十四、引用的形参和const
-
如果引用的数据对象类型不匹配,当引用为const时,C++将创建临时变量,让引用指向临时变量。
-
什么时候将创建临时变量呢?
-
引用是const。
-
数据对象的类型是正确的,但不是左值。
-
数据对象的类型不正确,但可以转换为正确的类型。
结论:如果函数的实参不是左值或与const引用形参的类型不匹配,那么C++将创建正确类型的匿名变量,将实参的值传递给匿名变量,并让形参来引用该变量。
将引用形参声明为const的理由有三个:
-
使用const可以避免无意中修改数据的编程错误。
-
使用const使函数能够处理const和非const实参,否则将只能接受非const实参。
-
使用const,函数能正确生成并使用临时变量。
-
左值是可以被引用的数据对象,可以通过地址访问它们,例如:变量、数组元素、结构体成员、引用和解引用的指针。
-
非左值包括字面常量(用双引号包含的字符串除外)和包含多项的表达式。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void func1(int no, string str) // 传值。 { cout << "亲爱的" << no << "号:" << str << endl; } void func2(const int* no,const string* str) // 传地址。 { cout << "亲爱的" << *no << "号:" << *str << endl; } void func3(const int& no, const string& str) // 传引用。 { cout << "亲爱的" << no << "号:" << str << endl; } int main() { //int bh = 3; // 超女的编号。 //string message = "我是一只傻傻鸟。"; // 向超女表白的内容。 // func1(8, "我是一只小小鸟。"); // func2(8, "我是一只小小鸟。"); func3('X', "我是一只小小鸟。"); ////func1(bh, message); // 传值。 ////func2(&bh, &message); // 传地址。 ////func3(bh, message); // 传引用。 //cout << "亲爱的" << bh << "号:" << message << endl; }
六十五、引用用于函数的返回值
传统的函数返回机制与值传递类似。
函数的返回值被拷贝到一个临时位置(寄存器或栈),然后调用者程序再使用这个值。
double m=sqrt(36); // sqrt()是求平方根函数。 sqrt(36)的返回值6被拷贝到临时的位置,然后赋值给m。 cout << sqrt(25);
sqrt(25)的返回值5被拷贝到临时的位置,然后传递给cout。
如果返回的是一个结构体,将把整个结构体拷贝到临时的位置。
如果返回引用不会拷贝内存。
语法:
返回值的数据类型& 函数名(形参列表);
注意:
- 如果返回局部变量的引用,其本质是野指针,后果不可预知。
- 可以返回函数的引用形参、类的成员、全局变量、静态变量。
- 返回引用的函数是被引用的变量的别名,将const用于引用的返回类型。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 const int &func2(int &ra) // 返回的是引用。 { ra++; cout << "ra的地址是:" << &ra << ",ra=" << ra << endl; return ra; } int main() { int a = 3; const int& b = func2(a); // 返回的是引用。 cout << " a的地址是:" << &a << ", a=" << a << endl; cout << " b的地址是:" << &b << ", b=" << b << endl; // func2(a) = 10; // 返回引有的函数是被引用的变量的别名。 // cout << " a的地址是:" << &a << ", a=" << a << endl; // cout << " b的地址是:" << &b << ", b=" << b << endl; }
六十六、各种形参的使用场景
传值、传地址和传引用的指导原则《C++ Primer Plus》
1.如果不需要在函数中修改实参
-
如果实参很小,如C++内置的数据类型或小型结构体,则按值传递。
-
如果实参是数组,则使用const指针,因为这是唯一的选择(没有为数组建立引用的说法)。
-
如果实参是较大的结构,则使用const指针或const引用。
-
如果实参是类,则使用const引用,传递类的标准方式是按引用传递(类设计的语义经常要求使用引用)。
2.如果需要在函数中修改实参 -
如果实参是内置数据类型,则使用指针。只要看到func(&x)的调用,表示函数将修改x。
-
如果实参是数组,则只能使用指针。
-
如果实参是结构体,则使用指针或引用。
-
如果实参是类,则使用引用。
-
当然,这只是一些指导原则,很可能有充分的理由做出其他的选择。
例如:对于基本类型,cin使用引用,因此可以使用cin>>a,而不是cin>>&a。
六十七、函数的默认参数
默认参数是指调用函数的时候,如果不书写实参,那么将使用的一个缺省值。
语法:返回值 函数名(数据类型 参数=值, 数据类型 参数=值,……);
注意:
- 如果函数的声明和定义是分开书写的,在函数声明中书写默认参数,函数的定义中不能书写默认参数。
- 函数必须从右到左设置默认参数。也就是说,如果要为某个参数设置默认值,则必须为它后面所有的参数设置默认值。
- 调用函数的时候,如果指定了某个参数的值,那么该参数前面所有的参数都必须指定。
示例:
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 void func(int bh,const string &name="西施", const string& message="我喜欢你。") // 向超女表白的函数。 { cout << "亲爱的"<<name<<"("<<bh<<"):" << message << endl; } int main() { func(3,"冰冰","我是一只傻傻鸟。"); func(5); }
六十八、函数重载
函数重载(函数多态)是指设计一系列同名函数,让它们完成相同(似)的工作。
C++允许定义名称相同的函数,条件是它们的特征(形参的个数、数据类型和排列顺序)不同。
#1 int func(short a ,string b); #2 int func(int a ,string b); #3 int func(double a,string b); #4 int func(int a ,string b, int len); #5 int func(string b , int a);
-
调用重载函数的时候,在代码中我们用相同的函数名,但是,后面的实参不一样,编译器根据实参与重载函数的形参进行匹配,然后决定调用具体的函数,如果匹配失败,编译器将视为错误。
-
在实际开发中,视需求重载各种数据类型,不要重载功能不同的函数。
注意:
- 使用重载函数时,如果数据类型不匹配,C++尝试使用类型转换与形参进行匹配,如果转换后有多个函数能匹配上,编译将报错。
- 引用可以作为函数重载的条件,但是,调用重载函数的时候,如果实参是变量,编译器将形参类型的本身和类型引用视为同一特征。
- 如果重载函数有默认参数,调用函数时,可能导致匹配失败。
- const不能作为函数重载的特征。
- 返回值的数据类型不同不能作为函数重载的特征。
- C++的名称修饰:编译时,对每个函数名进行加密,替换成不同名的函数。
void MyFunctionFoo(int,float); void MyFunctionFoo(long,float); ?MyFunctionFoo@@YAXH(int,float); #void MyFunctionFoo^$@(long,float);
示例:
#include <iostream> // 包含头文件。 using namespace std; void myswap(int& a, int& b) // 交换两个整型变量的值。 { int tmp = a; a = b; b = tmp; } void myswap(string& a, string& b) // 交换两个字符串变量的值。 { string tmp = a; a = b; b = tmp; } int main() { int a = 3, b = 5; myswap(a, b); cout << "a=" << a << ",b=" << b << endl; string c = "西施", d = "西瓜"; myswap(c, d); cout << "c=" << c << ",d=" << d << endl; }
六十九、内联函数
C- ++将内联函数的代码组合到程序中,可以提高程序运行的速度。
-
语法:在函数声明和定义前加上关键字inline。
-
通常的做法是将函数声明和定义写在一起。
注意:
- 内联函数节省时间,但消耗内存。
- 如果函数过大,编译器可能不将其作为内联函数。
- 内联函数不能递归。
示例:
#include <iostream> // 包含头文件。 using namespace std; inline void show(const short bh, const string message) // 表白函数。 { cout << "亲爱的" << bh << "号:" << message << endl; } int main() { //show(3, "我是一只傻傻鸟。"); { int bh = 3; string message = "我是一只傻傻鸟。"; cout << "亲爱的" << bh << "号:" << message << endl; } // show(8, "我有一只小小鸟。"); { int bh = 8; string message = "我有一只小小鸟。"; cout << "亲爱的" << bh << "号:" << message << endl; } // show(5, "我是一只小小鸟。"); { int bh = 5; string message = "我是一只小小鸟。"; cout << "亲爱的" << bh << "号:" << message << endl; } }
七十、从结构体到类
对面向对象编程来说,一切都是对象,对象用类来描述。
类把对象的数据和操作数据的方法作为一个整体考虑。
定义类的语法:
class 类名 { public: 成员一的数据类型 成员名一; 成员二的数据类型 成员名二; 成员三的数据类型 成员名三; ...... 成员n的数据类型 成员名n; };
注意:
- 类的成员可以是变量,也可以是函数。
- 类的成员变量也叫属性。
- 类的成员函数也叫方法/行为,类的成员函数可以定义在类的外面。
- 用类定义一个类的变量叫创建(或实例化)一个对象。
- 对象的成员变量和成员函数的作用域和生命周期与对象的作用域和生命周期相同。
#include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 struct st_girl // 超女基本信息结构体st_girl,存放了超女全部的数据项。 { string name; // 姓名。 int age; // 年龄。 int height; // 身高(cm)。 double weight; // 体重(kg)。 char sex='X'; // 性别:X-女;Y-男。 int yz; // 颜值:1-漂亮;2-一般;3-歪瓜裂枣。 string special; // 特长。 string memo; // 备注。 }; void setvalue(st_girl& girl, string name, int age, int height, double weight, char sex, int yz, string special, string memo) { girl.name = name; girl.age = age; girl.height = height; girl.weight = weight; girl.sex = sex; girl.yz = yz; girl.special = special; girl.memo = memo; } void show(const st_girl& girl) { cout << "姓名:" << girl.name << ",年龄:" << girl.age << ",身高:" << girl.height << ",体重:" << girl.weight << ",性别:" << girl.sex << ",颜值:" << girl.yz << ",特长:" << girl.special << ",备注:" << girl.memo << endl; } int main() { st_girl girl; setvalue(girl, "西施", 26, 170, 50.5, 'X', 1, "唱歌、跳舞、洗衣服。", "春秋第一美女,四大美女之一。"); show(girl); } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 struct st_girl // 超女基本信息结构体st_girl,存放了超女全部的数据项。 { string name; // 姓名。 int age; // 年龄。 void setvalue(string name1, int age1) // 设置成员变量的值。 { name = name1; age = age1; } void show() // 显示超女的自我介绍。 { cout << "姓名:" << name << ",年龄:" << age << endl; } }; int main() { st_girl girl; // 创建结构体变量。 girl.setvalue("西施", 26); // 设置成员变量的值。 girl.show(); // 显示超女的自我介绍。 } #include <iostream> // 包含头文件。 using namespace std; // 指定缺省的命名空间。 class CGirl // 超女类CGirl。 { public: string name; // 姓名。 int age; // 年龄。 void setvalue(string name1, int age1); // 设置成员变量的值。 void show() // 显示超女的自我介绍。 { cout << "姓名:" << name << ",年龄:" << age << endl; } }; void CGirl::setvalue(string name1, int age1) // 设置成员变量的值。 { name = name1; age = age1; } int main() { CGirl girl; // 创建超女对象。 girl.setvalue("西施", 26); // 设置成员变量的值。 girl.show(); // 显示超女的自我介绍。 }
本文作者:游客0721
本文链接:https://www.cnblogs.com/Gal0721/p/17725022.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步