8 数组
一些具有相同数据类型或相同属性(类)的数据的集合,用数据名标识,用下标或序号区分各个数据。数组中的数据称为元素。
8.1一维数组
定义一维数组的形式:数据类型 数据名[常量表达式]
初始化的形式:数据类型 数组名[常量表达式] = {初值表};
为数组的某一个元素赋值:数组名[下标] =值(下标从0开始)
数组的引用:数组名[下标]
初始化数组时,可以只给部分数组元素赋值
对全部元素数组赋值时,可以不指定数组长度,编译系统会根据初值个数确定数组的长度。
static型数组元素不赋初值,系统会自动默认为0。
int arr1[4] = {1,2,3,4};
int arr2[4] = { 1,2 };
int arr[4] = {0];//所有元素为0
static int arr3[3];
int arr4[4];
cout << “arr1:”<<arr1[0] << arr1[1] << arr1[2] << arr1[3] << endl;
cout << “arr2:” << arr2[0] << arr2[1] << arr2[2] << arr2[3] << endl;
cout << “arr3:” << arr3[0] << arr3[1] << arr3[2] << arr3[3] << endl;
cout << “arr4:” << arr4[0] << arr4[1] << arr4[2] << arr4[3] << endl;
1
2
3
4
5
6
7
8
9
在这里插入图片描述
8.2二维数组
定义一维数组的形式:数据类型 数据名[常量表达式1][常量表达式2]
初始化的形式:数据类型 数组名[常量表达式1] [常量表达式2]= {初值表};
为数组的某一个元素赋值:数组名[行下标][列下标] =值(下标从0开始)
数组的引用:数组名[行下标][列下标]
将所有数据写在一个花括号内,自动按照数组元素个数在内存中排列的顺序赋值
可对部分元素赋值,其余元素的值自动取0.
定义初始化数组时,可以省略第一维的长度,第二维不能省,系统会自动确认行数
int arr1[2][3];
int arr[2][3] = {0];//所有元素为0
int arr2[2][3] = { {1,2,3},{4,5,6} };
int arr3[2][3] = { 1,2,3 ,4,5,6 };
int arr4[2][3] = { {1},{4,6} };
int arr5[][3] = { 1,2,3 ,4,5,6 };
1
2
3
4
5
6
在这里插入图片描述
字符数组
char类型的数组,在字符数组中最后一位为’\0’)时,可以看成时字符串。在C++中定义了string类,在Visual C++中定义了Cstring类。
字符串中每一个字符占用一个字节,再加上最后一个空字符。如:
//字符串长度为8个字节,最后一位是’\0’。
char array[10] = “yuanrui”;//yuanrui\0\0\0
//也可以不用定义字符串长度,如:
char arr[] = “yuanrui”;//yuanrui\0
1
2
3
4
8.3 指向数组的指针
指针的概念会在后面详细讲解。
double *p;
double arr[10];
p = arr;//p = &arr[0];
*(p+3);//arr[4]
1
2
3
4
8.4 数组与new(动态创建数组)
一维数组:
int* arr1 = new int[2];//delete []arr1;
int* arr2 = new int[3]{ 1,2 };//delete []arr2
1
2
二维数组
int m=2, n=3;
int** arr3 = new int*[2];//delete []arr3
for (int i = 0; i < 10; ++i)
{
arr3[i] = new int[3]; // delete []arr3[i]
}
int* arr4 = new int[m*n];//数据按行存储 delete []arr3
1
2
3
4
5
6
7
8.5 数组与函数
数组->函数
如果传递二维数组,形参必须制定第二维的长度。
形式参数是一个指针:void function(int param)
形式参数是一个已定义大小的数组:void function(int param[10])
形式参数是一个未定义大小的数组:void function(int param[])
二维数组:void function(int a[][3],int size)
函数返回数组
C++ 不支持在函数外返回局部变量的地址,除非定义局部变量为 static 变量。
int * function();
int* function();
1
2
8.6 获取数组的大小
动态创建(new)的基本数据类型数组无法取得数组大小
int a[3];
//第一种方法
cout<<sizeof(a)/sizeof(a[0])<<endl;
//第二种方法
cout << end(a) - begin(a) << endl;
//二维数组
int arr[5][3];
int lines = sizeof(arr) / sizeof(arr[0][0]);
int row = sizeof(arr) / sizeof(arr[0]);//行
int col = lines / row;//列
cout << row << “::”<<col << endl;
cout << end(arr) - begin(arr) << endl;//5行
1
2
3
4
5
6
7
8
9
10
11
12
9 函数
函数是实现模块化程序设计思想的重要工具, C++程序中每一项操作基本都是由一个函数来实现的,C++程序中只能有一个主函数(main)
9.1 函数声明与定义
函数类型-函数的返回值类型;函数名-必须符合C++标识符命名规则,后面必须跟一对括号;函数体-实现函数功能的主题部分;参数列表-函数名后面的括号内,用于向函数传递数值或带回数值。
函数声明中,参数名可以省略,参数类型和函数的类型不能省略。
函数声明可以放在主调函数内部,放在调用语句之前;也可以放在主调函数外,如果位于所有定义函数之前,后面函数定义顺序任意,各个主调函数调用也不必再做声明
当函数定义在前,函数调用灾后,可以不用函数声明。
后两条总结一下就是:调用函数前,程序得知道有这个函数,声明就是提前让程序知道有这么的玩意
函数声明:
函数类型 函数名(参数列表);
eg:
int max(int a,int b);//声明函数时,a,b可以省略
int max(int,int);
void show();
1
2
3
4
5
函数定义:
函数类型 函数名(参数列表)
{
函数体;
}
eg:
int max(int a,int b)
{
int z;
z = a>b?a:b;
return z;
}
1
2
3
4
5
6
7
8
9
10
11
9.2 函数的参数与返回值
形参:函数定义后面括号里的参数,函数调用前不占内存。
实参:函数调用括号里的参数,可以是常量,变量或表达式等。
形参和实参必须个数相同、类型一致,顺序一致
函数传递方式:传值,指针,引用
关于指针和引用后面有详细介绍。
//传值-修改函数内的形式参数对实际参数没有影响
int add(int value)
{
value++;
return value;
}
int main()
{
int v = 10;
cout << "add() = " << add(v) << endl;//add() = 11
cout << "v = " << v << endl;//v = 10
return 0;
}
//指针-修改形式参数会影响实际参数
int add(int* pValue)
{
(*pValue)++;
return *pValue;
}
int main()
{
int v = 10;
cout << "add() = " << add(&v) << endl;//add() = 11
cout << "v = " << v << endl;//v = 11
return 0;
}
//引用-修改形式参数会影响实际参数
int add(int &value)
{
value++;
return value;
}
int main()
{
int v = 10;
cout << "add() = " << add(v) << endl;//add() = 11
cout << "v = " << v << endl;//v = 11
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
有默认值参数的函数
int sum(int a, int b=2)
{
return (a + b);
}
int main ()
{
cout << “Total value is :” << sum(100, 200);<< endl;//Total value is :300
cout << “Total value is :” << sum(100);<< endl;//Total value is :102
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
函数的返回值
返回值通过return给出,return后面跟表达式,且只能放回一个值;如果没有表达式,可以不写return;return后面的括号可有可无。
return语句中的表达式类型应与函数类型一致,否则自动转换类型(函数类型决定返回值类型)
9.3 函数调用
函数可以单独作为一个语句使用。有返回值的函数,可将函数调用作为语句的一部分,利用返回值参与运算。
函数调用形式:参数传递–>函数体执行–>返回主调函数
函数名(实参列表);
show();
1
2
函数的嵌套调用:
int a()
{
return 666;
}
int b(int sum)
{
return sum+a()
}
int main()
{
cout<<b(222)<<endl;//888
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
函数的递归调用:直接递归调用和间接递归调用
一个函数直接或间接递归调用该函数本身,称为函数的递归调用
递归和回归:原问题=>子问题 子问题的解=>原问题的解
//直接递归调用:求1+…n的值
int total(int sum)
{
if (sum == 1)
{
return 1;
}
return sum + total(sum - 1);
}
int main()
{
cout << "total = " << total(10) << endl;//total = 55
system(“pause”);
return 0;
}
//间接递归调用
int f2();
int f1()
{
…
f2()
}
int f2()
{
f1();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
9.4 函数重载
同一个函数名对应不同的函数实现,每一类实现对应着一个函数体,名字相同,功能相同,只是参数的类型或参数的个数不同。
多个同名函数只是函数类型(函数返回值类型)不同时,它们不是重载函数
int add(int a,int b)
{
return a+b;
}
double add(double a,double b)
{
return a+b;
}
int add(int a,int b,int c)
{
return a+b+c;
}
1
2
3
4
5
6
7
8
9
10
11
12
9.5 内联(inline)函数
c++在编译时可以讲调用的函数代码嵌入到主调函数中,这种嵌入到主调函数中的函数称为内联函数,又称为内嵌函数或内置函数。
定义内联函数时,在函数定义和函数原型声明时都使用inline,也可以只在其中一处使用,其效果一样。
内联函数在编译时用内联函数函数的函数体替换,所以不发生函数调用,不需要保护现场,恢复现场,节省了开销。
内联函数增加了目标程序的代码量。因此,一般只将函数规模很小且使用频繁的函数声明为内联函数。
当内联函数中实现过于复杂时,编译器会将它作为一个普通函数处理,所以内联函数内不能包含循环语句和switch语句。
内联函数格式如下:
inline 函数类型 函数名(形参列表)
{
函数体;
}
inline int add(int a, int b)
{
return a + b;
}
1
2
3
4
5
6
7
8
9.6 洞悉内联函数底层原理
1.使用Visual Studio 2015创建一个C++Win32控制台程序,点击项目->项目属性设置内联函数优化
在这里插入图片描述
2.编写内联函数代码,设置断点,debug启动
#include
#include
using namespace std;
inline int add(int a, int b)
{
return a + b;//断点1
}
int main()
{
int result = add(12, 34);
cout << result << endl;//断点2
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
3.调试->窗口->反汇编,然后就能看到编译后的汇编程序
…
int result = add(12, 34);
00B620DE mov eax,0Ch
00B620E3 add eax,22h //对eax中和22h中值进行相加,赋值给eax
00B620E6 mov dword ptr [result],eax
cout << result << endl;
00B620E9 mov esi,esp
00B620EB push offset std::endl<char,std::char_traits > (0B610A5h)
00B620F0 mov edi,esp
00B620F2 mov eax,dword ptr [result]
00B620F5 push eax
00B620F6 mov ecx,dword ptr [imp?cout@std@@3V?
b
a
s
i
c
o
s
t
r
e
a
m
@
D
U
?
basic_ostream@DU?
basicostream@DU?char_traits@D@std@@@1@A (0B6D098h)]
00B620FC call dword ptr [__imp_std::basic_ostream<char,std::char_traits >::operator<< (0B6D0A8h)]
00B62102 cmp edi,esp
00B62104 call __RTC_CheckEsp (0B611C7h)
00B62109 mov ecx,eax
00B6210B call dword ptr [__imp_std::basic_ostream<char,std::char_traits >::operator<< (0B6D0ACh)]
00B62111 cmp esi,esp
00B62113 call __RTC_CheckEsp (0B611C7h)
return 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
4.从汇编代码中可以代码编译后内联函数直接嵌入到主函数中,并且断点1不会执行到,下面是没使用内联函数(去掉inline关键字)的汇编代码:
int result = add(12, 34);
00291A4E push 22h
00291A50 push 0Ch
00291A52 call add (02914D8h) //调用add函数
00291A57 add esp,8//移动堆栈指针esp,继续执行主函数
00291A5A mov dword ptr [result],eax
cout << result << endl;
00291A5D mov esi,esp
00291A5F push offset std::endl<char,std::char_traits > (02910A5h)
00291A64 mov edi,esp
00291A66 mov eax,dword ptr [result]
00291A69 push eax
00291A6A mov ecx,dword ptr [imp?cout@std@@3V?
b
a
s
i
c
o
s
t
r
e
a
m
@
D
U
?
basic_ostream@DU?
basicostream@DU?char_traits@D@std@@@1@A (029D098h)]
cout << result << endl;
00291A70 call dword ptr [__imp_std::basic_ostream<char,std::char_traits >::operator<< (029D0A8h)]
00291A76 cmp edi,esp
00291A78 call __RTC_CheckEsp (02911C7h)
00291A7D mov ecx,eax
00291A7F call dword ptr [__imp_std::basic_ostream<char,std::char_traits >::operator<< (029D0ACh)]
00291A85 cmp esi,esp
00291A87 call __RTC_CheckEsp (02911C7h)
system(“pause”);
00291A8C mov esi,esp
00291A8E push offset string “pause” (0299B30h)
00291A93 call dword ptr [__imp__system (029D1DCh)]
00291A99 add esp,4
00291A9C cmp esi,esp
00291A9E call __RTC_CheckEsp (02911C7h)
return 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
从以上代码代码可以看出,在主函数中调用(call)了add函数。
5.在内联函数中添加几个循环后,编译器就把内联函数当做普通函数看待了,代码如下:
inline int add(int a, int b)
{
int sum = 0;
for (int i = 0; i < 100; i++)
a++;
for (int i = 0; i < 100; i++)
{
for (int i = 0; i < 100; i++)
{
sum++;
}
}
return a + b;
}
int main()
{
int result = add(12, 34);
cout << result << endl;
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int result = add(12, 34);
00181A4E push 22h
00181A50 push 0Ch
00181A52 call add (01814ECh) ///
00181A57 add esp,8
00181A5A mov dword ptr [result],eax
cout << result << endl;
00181A5D mov esi,esp
00181A5F push offset std::endl<char,std::char_traits > (01810A5h)
00181A64 mov edi,esp
00181A66 mov eax,dword ptr [result]
00181A69 push eax
00181A6A mov ecx,dword ptr [imp?cout@std@@3V?
b
a
s
i
c
o
s
t
r
e
a
m
@
D
U
?
basic_ostream@DU?
basicostream@DU?char_traits@D@std@@@1@A (018D098h)]
cout << result << endl;
00181A70 call dword ptr [__imp_std::basic_ostream<char,std::char_traits >::operator<< (018D0A8h)]
00181A76 cmp edi,esp
00181A78 call __RTC_CheckEsp (01811C7h)
00181A7D mov ecx,eax
00181A7F call dword ptr [__imp_std::basic_ostream<char,std::char_traits >::operator<< (018D0ACh)]
00181A85 cmp esi,esp
00181A87 call __RTC_CheckEsp (01811C7h)
return 0;
00181AA3 xor eax,eax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
10 字符串(string)
10.1 C风格的字符串(字符数组)
C风格的字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。
输入字符串长度一定小于已定义的字符数组长度,最后一位是/0终止符号;不然输出时无法知道在哪里结束。
字符数组的定义和初始化
char a[5]
//字符个数不够,补0; 字符个数超过报错
char str[7] = {‘h’,‘e’,‘i’,‘r’,‘e’,‘n’};
char str[] = {‘h’,‘e’,‘i’,‘r’,‘e’,‘n’};
cin>>str;//输入 输入字符串长度一定小于已定义的字符数组长度
cout<<str;//输出
1
2
3
4
5
6
字符串的处理函数
strcat(char s1[],const char s2[]);//将s2接到s1上
strcpy(char s1[],const char s2[]);//将s2复制到s1上
strcmp(const char s1[],const char s2[]);//比较s1,s2 s1>s2返回1 相等返回1,否则返回-1
strlen(char s[]);//计算字符串s的长度 字符串s的实际长度,不包括\0在内
1
2
3
4
10.2 C++中的字符串(string)
字符串的定义和初始化
//定义
string 变量;
string str1;
//赋值
string str2 = “ShangHai”;
string str3 = str2;
str3[3] = ‘2’;//对某个字符赋值
//字符串数组
string 数组名[常量表达式]
string arr[3];
1
2
3
4
5
6
7
8
9
10
字符串的处理函数
#include
#include
#include
string str;//生成空字符串
string s(str);//生成字符串为str的复制品
string s(str, strbegin,strlen);//将字符串str中从下标strbegin开始、长度为strlen的部分作为字符串初值
string s(cstr, char_len);//以C_string类型cstr的前char_len个字符串作为字符串s的初值
string s(num ,c);//生成num个c字符的字符串
string s(str, stridx);//将字符串str中从下标stridx开始到字符串结束的位置作为字符串初值
size()和length();//返回string对象的字符个数
max_size();//返回string对象最多包含的字符数,超出会抛出length_error异常
capacity();//重新分配内存之前,string对象能包含的最大字符数
,>=,<,<=,==,!=//支持string与C-string的比较(如 str<”hello”)。 使用>,>=,<,<=这些操作符的时候是根据“当前字符特性”将字符按字典顺序进行逐一得 比较,string (“aaaa”) <string(aaaaa)。
compare();//支持多参数处理,支持用索引值和长度定位子串来进行比较。返回一个整数来表示比较结果,返回值意义如下:0:相等 1:大于 -1:
push_back()
insert( size_type index, size_type count, CharT ch );//在index位置插入count个字符ch
insert( size_type index, const CharT* s );//index位置插入一个常量字符串
insert( size_type index, const CharT* s, size_type n);//index位置插入常量字符串
insert( size_type index, const basic_string& str );//index位置插入常量string中的n个字符
insert( size_type index, const basic_string& str, size_type index_str, size_type n);//index位置插入常量str的从index_str开始的n个字符
insert( size_type index, const basic_string& str,size_type index_str, size_type count = npos);//index位置插入常量str从index_str开始的count个字符,count可以表示的最大值为npos.这个函数不构成重载 npos表示一个常数,表示size_t的最大值,string的find函数如果未找到指定字符,返回的就是一个npos
iterator insert( iterator pos, CharT ch );
iterator insert( const_iterator pos, CharT ch );
void insert( iterator pos, size_type n, CharT ch );//迭代器指向的pos位置插入n个字符ch
iterator insert( const_iterator pos, size_type count, CharT ch );//迭代器指向的pos位置插入count个字符ch
void insert( iterator pos, InputIt first, InputIt last );
iterator insert( const_iterator pos, InputIt first, InputIt last );
append() 和 + 操作符
//访问string每个字符串
string s1(“yuanrui”); // 调用一次构造函数
// 方法一: 下标法
for( int i = 0; i < s1.size() ; i++ )
cout<<s1[i];
// 方法二:正向迭代器
for( string::iterator iter = s1.begin();; iter < s1.end() ; iter++)
cout<<iter;
// 方法三:反向迭代器
for(string::reverse_iterator riter = s1.rbegin(); ; riter < s1.rend() ; riter++)
cout<<riter;
iterator erase(iterator p);//删除字符串中p所指的字符
iterator erase(iterator first, iterator last);//删除字符串中迭代器区间[first,last)上所有字符
string& erase(size_t pos = 0, size_t len = npos);//删除字符串中从索引位置pos开始的len个字符
void clear();//删除字符串中所有字符
string& replace(size_t pos, size_t n, const char s);//将当前字符串从pos索引开始的n个字符,替换成字符串s
string& replace(size_t pos, size_t n, size_t n1, char c); //将当前字符串从pos索引开始的n个字符,替换成n1个字符c
string& replace(iterator i1, iterator i2, const char s);//将当前字符串[i1,i2)区间中的字符串替换为字符串s
//tolower()和toupper()函数 或者 STL中的transform算法
string s = “ABCDEFG”;
for( int i = 0; i < s.size(); i++ )
s[i] = tolower(s[i]);
transform(s.begin(),s.end(),s.begin(),::tolower);
size_t find (constchar s, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找子串s,返回找到的位置索引,-1表示查找不到子串
size_t find (charc, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找字符c,返回找到的位置索引,-1表示查找不到字符
size_t rfind (constchar s, size_t pos = npos) const;//在当前字符串的pos索引位置开始,反向查找子串s,返回找到的位置索引,-1表示查找不到子串
size_t rfind (charc, size_t pos = npos) const;//在当前字符串的pos索引位置开始,反向查找字符c,返回找到的位置索引,-1表示查找不到字符
size_tfind_first_of (const char* s, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_tfind_first_not_of (const char* s, size_t pos = 0) const;//在当前字符串的pos索引位置开始,查找第一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_t find_last_of(const char* s, size_t pos = npos) const;//在当前字符串的pos索引位置开始,查找最后一个位于子串s的字符,返回找到的位置索引,-1表示查找不到字符
size_tfind_last_not_of (const char* s, size_t pos = npos) const;//在当前字符串的pos索引位置开始,查找最后一个不位于子串s的字符,返回找到的位置索引,-1表示查找不到子串
sort(s.begin(),s.end());
substr(pos,n);//返回字符串从下标pos开始n个字符
strtok()
char str[] = “I,am,a,student; hello world!”;
const char *split = “,; !”;
char *p2 = strtok(str,split);
while( p2 != NULL )
{
cout<<p2<<endl;
p2 = strtok(NULL,split);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71