c++学习笔记(2)

50、指针的基本概念
1)变量的地址
变量是内存变量的简称,在 C++中,每定义一个变量,系统就会给变量分配一块内存,内存是有地
址的。
C++用运算符&获取变量在内存中的起始地址。
语法:&变量名
2)指针变量
指针变量简称指针,它是一种特殊的变量,专用于存放变量在内存中的起始地址。
语法:数据类型 *变量名;
数据类型必须是合法的 C++数据类型(int、char、double 或其它自定义的数据类型)。
星号*与乘法中使用的星号是相同的,但是,在这个场景中,星号用于表示这个变量是指针。
3)对指针赋值
不管是整型、浮点型、字符型,还是其它的数据类型的变量,它的地址都是一个十六进制数。我们用
整型指针存放整数型变量的地址;用字符型指针存放字符型变量的地址;用浮点型指针存放浮点型变量的
地址,用自定义数据类型指针存放自定义数据类型变量的地址。
语法:指针=&变量名;
注意
 对指针的赋值操作也通俗的被称为“指向某变量”,被指向的变量的数据类型称为“基类型”。
 如果指针的数据类型与基类型不符,编译会出现警告。但是,可以强制转换它们的类型。
4)指针占用的内存
指针也是变量,是变量就要占用内存空间。
在 64 位的操作系统中,不管是什么类型的指针,占用的内存都是 8 字节。
在 C++中,指针是复合数据类型,复合数据类型是指基于其它类型而定义的数据类型,在程序中,int
是整型类型,int*是整型指针类型,int*可以用于声明变量,可以用于 sizeof 运算符,可以用于数据类型
的强制转换,总的来说,把 int*当成一种数据类型就是了。
51、使用指针
声明指针变量后,在没有赋值之前,里面是乱七八糟的值,这时候不能使用指针。
指针存放变量的地址,因此,指针名表示的是地址(就像变量名可以表示变量的值一样)
*运算符被称为间接值或解除引用(解引用)运算符,将它用于指针,可以得到该地址的内存中存储
的值,*也是乘法符号,C++根据上下文来确定所指的是乘法还是解引用。
变量和指向变量的指针就像同一枚硬币的两面。
哪个银行? 什么东西? 数额
程序在存储数据的时候,必须跟踪三种基本属性:
 数据存储在哪里;
 数据是什么类型;
 数据的值是多少。
用两种策略可以达到以上目的:
声明一个普通变量,声明时指出数据类型和变量名(符号名),系统在内部跟踪该内存单元。
声明一个指针变量,存储的值是地址,而不是值本身,程序直接访问该内存单元。
52、指针用于函数的参数
如果把函数的形参声明为指针,调用的时候把实参的地址传进去,形参中存放的是实参的地址,在函
数中通过解引用的方法直接操作内存中的数据,可以修改实数的值,这种方法被通俗的称为地址传递或传
地址。
值传递:函数的形参是普通变量。
传地址的意义如下:
 可以在函数中修改实参的值。
 减少内存拷贝,提升性能。
示例:
#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;
}
53、用 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;
}
54、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);
}
55、C++内存模型
在 C++ 中,程序运行时,内存主要分成四个区,分别是栈、堆、数据段和代码段。
栈:存储局部变量、函数参数和返回值。
堆:存储动态开辟内存的变量。
数据段:存储全局变量和静态变量。
代码段:存储可执行程序的代码和常量(例如字符常量),此存储区不可修改。
栈和堆的主要区别:
1)管理方式不同:栈是系统自动管理的,在出作用域时,将自动被释放;堆需手动释放,若程序中
不释放,程序结束时由操作系统回收。
2)空间大小不同:堆内存的大小受限于物理内存空间;而栈就小得可怜,一般只有 8M(可以修改
系统参数)。
3)分配方式不同:堆是动态分配;栈有静态分配和动态分配(都是自动释放)。
4)分配效率不同:栈是系统提供的数据结构,计算机在底层提供了对栈的支持,进栈和出栈有专门
的指令,效率比较高;堆是由 C++函数库提供的。
5)是否产生碎片:对于栈来说,进栈和出栈都有着严格的顺序(先进后出),不会产生碎片;而堆
频繁的分配和释放,会造成内存空间的不连续,容易产生碎片,太多的碎片会导致性能的下降。
6)增长方向不同:栈向下增长,以降序分配内存地址;堆向上增长,以升序分配内存地址。
56、动态分配内存 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;
}/*
}
57、二级指针
指针是指针变量的简称,也是变量,是变量就有地址。
指针用于存放普通变量的地址。
二级指针用于存放指针变量的地址。
声明二级指针的语法:数据类型** 指针名;
使用指针有两个目的: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 = &ii; 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;
}
58、空指针
在 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;
}
59、野指针
野指针就是指针指向的不是一个有效(合法)的地址。
在程序中,如果访问野指针,可能会造成程序的崩溃。
出现野指针的情况主要有三种:
1)指针在定义的时候,如果没有进行初始化,它的值是不确定的(乱指一气)。
2)如果用指针指向了动态分配的内存,内存被释放后,指针不会置空,但是,指向的地址已失效。
3)指针指向的变量已超越变量的作用域(变量的内存空间已被系统回收),让指针指向了函数的局
部变量,或者把函数的局部变量的地址作为返回值赋给了指针。
规避方法:
1)指针在定义的时候,如果没地方指,就初始化为 nullptr。
2)动态分配的内存被释放后,将其置为 nullptr。
3)函数不要返回局部变量的地址。
注意:野指针的危害比空指针要大很多,在程序中,如果访问野指针,可能会造成程序的崩溃。是可
能,不是一定,程序的表现是不稳定,增加了调试程序的难度。
60、函数指针
函数的二进制代码存放在内存四区中的代码段,函数的地址是它在内存中的起始地址。如果把函数的
地址作为参数传递给函数,就可以在函数中灵活的调用其它函数。
使用函数指针的三个步骤:
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); // 李四要表白。
}
61、一维数组的基本概念
数组是一组数据类型相同的变量,可以存放一组数据。
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;
}
}
62、一维数组和指针
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;
}
63、一维数组用于函数的参数
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));
}
64、用 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;
}
65、一维数组的排序 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;
}
}
66、一维数组的查找-折半查找
折半查找也叫二分查找,只适用于已排序的数组(升序降序都可以)。
示例:
#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";
}
67、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;
}
68、二维数组
一维数组的数学概念是线性表,二维数组的数学概念是矩阵。
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; // 一维数组的数组表示法。
}
}
69、二维数组用于函数的参数
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; // 每显示一个方阵后,输出两个换行符。
}
}
70、结构体的基本概念
结构体是用户自定义的类型,可以将多种数据的表示合并到一起,描述一个完整的对象。
使用结构体有两个步骤: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;
}
71、结构体指针
结构体是一种自定义的数据类型,用结构体可以创建结构体变量。
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;
}
72、结构体数组
结构体可以被定义成数组变量,本质上与其它类型的数组变量没有区别。
声明结构体数组的语法: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;
}
}
73、结构体嵌入数组和结构体
示例:
#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;
}
75、简单链表
如果结构体中有一个本结构体的指针,它就是链表。
struct st_girl
{
int no;
string name;
struct st_girl *next;
};
0x302
0x201
01 西施 0x201
0x02086 冰冰
03 幂幂 0x033
示例:
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
struct st_girl // 超女单链表。
{
int no; // 超女编号。
string name; // 超女姓名。
struct st_girl* next; // 下一个超女节点的地址,如果本节点是最后一条记录,填 nullptr。
};
int main()
{
st_girl *head = nullptr, *tail = nullptr, *tmp = nullptr; // head 头指针、tail 尾指针、tmp
临时指针。
tmp=new st_girl({ 1, "西施", nullptr }); // 分配第一个节点。
head = tail = tmp;
tmp = new st_girl({ 6, "冰冰", nullptr }); // 分配第二个节点。
tail->next = tmp; // 把上一个节点的 next 指针指向新节点。
tail = tmp; // 移动尾指针,让尾指针指向刚分配出来的新节点。
tmp = new st_girl({ 3, "幂幂", nullptr }); // 分配第三个节点。
tail->next = tmp; // 把上一个节点的 next 指针指向新节点。
tail = tmp; // 移动尾指针,让尾指针指向刚分配出来的新节点。
0x087
0x033
0x028
// 遍历链表。
tmp = head; // 从头节点开始。
while (tmp != nullptr)
{
cout << "no=" << tmp->no << "name=" << tmp->name << "tmp->next=" <<
tmp->next << endl;
tmp = tmp->next; // 顺着 next 指向往后面找。
}
// 释放链表。
while (head != nullptr)
{
tmp = head; // 让临时节点指向头节点。
head = head->next; // 头节点后移。
delete tmp; // 删除临时节点。
}
}
76、共同体
共同体(共用体、联合体)是一种数据格式,它能存储不同的数据类型,但是,在同一时间只能存储
其中的一种类型。
声明共同体的语法:
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;
}
77、枚举
枚举是一种创建符号常量的方法。
枚举的语法:
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";
}
}
78、引用的基本概念
引用变量是 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;
}
79、引用的本质
引用是指针常量的伪装。
引用是编译器提供的一个有用且安全的工具,去除了指针的一些缺点,禁止了部分不安全的操作。
变量是什么?变量就是一个在程序执行过程中可以改变的量。
换一个角度,变量是一块内存区域的名字,它代表了这块内存区域,当我们对变量进行修改的时候,
会引起内存区域中内容的改变。
在计算机看来,内存区域根本就不存在什么名字,它仅有的标志就是它的地址,因此我们若想修改一
块内存区域的内容,只有知道他的地址才能实现。
所谓的变量只不过是编译器给我们进行的一种抽象,让我们不必去了解更多的细节,降低我们的思维
跨度而已。
程序员拥有引用,但编译器仅拥有指针(地址)。
引用的底层机制实际上是和指针一样的。不要相信有别名,不要认为引用可以节省一个指针的空间,
因为这一切不会发生,编译器还是会把引用解释为指针。
引用和指针本质上没有区别。
示例:
#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;
}
80、引用用于函数的参数
把函数的形参声明为引用,调用函数的时候,形参将成为实参的别名。
这种方法也叫按引用传递或传引用。(传值、传地址、传引用只是说法不同,其实都是传值。)
引用的本质是指针,传递的是变量的地址,在函数中,修改形参会影响实参。
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;
}
81、引用的形参和 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;
}
82、引用用于函数的返回值
传统的函数返回机制与值传递类似。
函数的返回值被拷贝到一个临时位置(寄存器或栈),然后调用者程序再使用这个值。
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;
}
83、各种形参的使用场景
传值、传地址和传引用的指导原则《C++ Primer Plus》
1)如果不需要在函数中修改实参
 如果实参很小,如 C++内置的数据类型或小型结构体,则按值传递。
 如果实参是数组,则使用 const 指针,因为这是唯一的选择(没有为数组建立引用的说法)。
 如果实参是较大的结构,则使用 const 指针或 const 引用。
 如果实参是类,则使用 const 引用,传递类的标准方式是按引用传递(类设计的语义经常要求使
用引用)。
2)如果需要在函数中修改实参
 如果实参是内置数据类型,则使用指针。只要看到 func(&x)的调用,表示函数将修改 x。
 如果实参是数组,则只能使用指针。
 如果实参是结构体,则使用指针或引用。
 如果实参是类,则使用引用。
当然,这只是一些指导原则,很可能有充分的理由做出其他的选择。
例如:对于基本类型,cin 使用引用,因此可以使用 cin>>a,而不是 cin>>&a。
84、函数的默认参数
默认参数是指调用函数的时候,如果不书写实参,那么将使用的一个缺省值。
语法:返回值 函数名(数据类型 参数=值, 数据类型 参数=值,……);
注意:
 如果函数的声明和定义是分开书写的,在函数声明中书写默认参数,函数的定义中不能书写默认
参数。
 函数必须从右到左设置默认参数。也就是说,如果要为某个参数设置默认值,则必须为它后面所
有的参数设置默认值。
 调用函数的时候,如果指定了某个参数的值,那么该参数前面所有的参数都必须指定。
示例:
#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);
}
85、函数重载
函数重载(函数多态)是指设计一系列同名函数,让它们完成相同(似)的工作。
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;
}
86、内联函数
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;
}
}
 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值