本文将使用最快的方法过完C++基础知识。由于是快速阅览,本文不会描述过多的详细知识,如何深入还需要各位努力;不过学习过了一遍内容后,再学习C++也会较为容易。
更多进阶教程可以关注微信公众号 “C和C加加” 回复“ZXC”即可免费获取整理不易,如有错误请多包涵!八.数组一些具有相同数据类型或相同属性(类)的数据的集合,用数据名标识,用下标或序号区分各个数据。数组中的数据称为元素。
1.一维数组定义一维数组的形式:数据类型 数据名[常量表达式]
初始化的形式:数据类型 数组名[常量表达式] = {初值表};
为数组的某一个元素赋值:数组名[下标] =值(下标从0开始)
数组的引用:数组名[下标]
- 初始化数组时,可以只给部分数组元素赋值
- 对全部元素数组赋值时,可以不指定数组长度,编译系统会根据初值个数确定数组的长度。
static型数组元素不赋初值,系统会自动默认为0。
int arr1[4] = {1,2,3,4};int arr2[4] = { 1,2 };int arr[4] = {0];//所有元素为0static 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;2.二维数组
定义一维数组的形式:数据类型 数据名[常量表达式1][常量表达式2]
初始化的形式:数据类型 数组名[常量表达式1] [常量表达式2]= {初值表};
为数组的某一个元素赋值:数组名[行下标][列下标] =值(下标从0开始)
数组的引用:数组名[行下标][列下标]
- 将所有数据写在一个花括号内,自动按照数组元素个数在内存中排列的顺序赋值
- 可对部分元素赋值,其余元素的值自动取0.
- 定义初始化数组时,可以省略第一维的长度,第二维不能省,系统会自动确认行数
int arr1[2][3];int arr[2][3] = {0];//所有元素为0int 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 };
字符数组
char类型的数组,在字符数组中最后一位为’\0’)时,可以看成时字符串。在C++中定义了string类,在Visual C++中定义了Cstring类。
字符串中每一个字符占用一个字节,再加上最后一个空字符。如:
//字符串长度为8个字节,最后一位是'\0'。char array[10] = "yuanrui";//yuanrui\0\0\0//也可以不用定义字符串长度,如:char arr[] = "yuanrui";//yuanrui\03.指向数组的指针
指针的概念会在后面详细讲解。
double *p;double arr[10];p = arr;//p = &arr[0];*(p+3);//arr[3]4.数组与new(动态创建数组)
一维数组:
int* arr1 = new int[2];//delete []arr1;int* arr2 = new int[3]{ 1,2 };//delete []arr2
二维数组
int m=2, n=3;int** arr3 = new int*[2];//delete []arr3for (int i = 0; i < 10; ++i){ arr3[i] = new int[3]; // delete []arr3[i]}int* arr4 = new int[m*n];//数据按行存储 delete []arr35.数组与函数
数组->函数
如果传递二维数组,形参必须制定第二维的长度。
形式参数是一个指针: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();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行九.函数
函数是实现模块化程序设计思想的重要工具, C++程序中每一项操作基本都是由一个函数来实现的,C++程序中只能有一个主函数(main)
1.函数声明与定义- 函数类型-函数的返回值类型;函数名-必须符合C++标识符命名规则,后面必须跟一对括号;函数体-实现函数功能的主题部分;参数列表-函数名后面的括号内,用于向函数传递数值或带回数值。
- 函数声明中,参数名可以省略,参数类型和函数的类型不能省略。
- 函数声明可以放在主调函数内部,放在调用语句之前;也可以放在主调函数外,如果位于所有定义函数之前,后面函数定义顺序任意,各个主调函数调用也不必再做声明
- 当函数定义在前,函数调用灾后,可以不用函数声明。
后两条总结一下就是:调用函数前,程序得知道有这个函数,声明就是提前让程序知道有这么的玩意
函数声明:
函数类型 函数名(参数列表);eg:int max(int a,int b);//声明函数时,a,b可以省略int max(int,int);void show();
函数定义:
函数类型 函数名(参数列表){函数体;}eg:int max(int a,int b){int z;z = a>b?a:b;return z;}
2.函数的参数与返回值
- 形参:函数定义后面括号里的参数,函数调用前不占内存。
- 实参:函数调用括号里的参数,可以是常量,变量或表达式等。
形参和实参必须个数相同、类型一致,顺序一致
函数传递方式:传值,指针,引用
关于指针和引用后面有详细介绍。
//传值-修改函数内的形式参数对实际参数没有影响int add(int value){value++;return value;}int main(){int v = 10;cout << "add() = " << add(v) << endl;//add() = 11cout << "v = " << v << endl;//v = 10return 0;}//指针-修改形式参数会影响实际参数int add(int* pValue){(*pValue)++;return *pValue;}int main(){int v = 10;cout << "add() = " << add(&v) << endl;//add() = 11cout << "v = " << v << endl;//v = 11return 0;}//引用-修改形式参数会影响实际参数int add(int &value){value++;return value;}int main(){int v = 10;cout << "add() = " << add(v) << endl;//add() = 11cout << "v = " << v << endl;//v = 11return 0;}
有默认值参数的函数
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;}
函数的返回值
- 返回值通过return给出,return后面跟表达式,且只能放回一个值;如果没有表达式,可以不写return;return后面的括号可有可无。
- return语句中的表达式类型应与函数类型一致,否则自动转换类型(函数类型决定返回值类型)
- 函数可以单独作为一个语句使用。有返回值的函数,可将函数调用作为语句的一部分,利用返回值参与运算。
函数调用形式:参数传递–>函数体执行–>返回主调函数
函数名(实参列表);show();
函数的嵌套调用:
int a(){return 666;}int b(int sum){return sum+a()}int main(){cout<<b(222)<<endl;//888return 0;}
函数的递归调用:直接递归调用和间接递归调用
- 一个函数直接或间接递归调用该函数本身,称为函数的递归调用
- 递归和回归:原问题=>子问题 子问题的解=>原问题的解
//直接递归调用:求1+...n的值int total(int sum){if (sum == 1){return 1;}return sum + total(sum - 1);}int main(){cout << "total = " << total(10) << endl;//total = 55system("pause");return 0;}//间接递归调用int f2();int f1(){... f2()}int f2(){f1();}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;}5.内联(inline)函数
c++在编译时可以讲调用的函数代码嵌入到主调函数中,这种嵌入到主调函数中的函数称为内联函数,又称为内嵌函数或内置函数。
- 定义内联函数时,在函数定义和函数原型声明时都使用inline,也可以只在其中一处使用,其效果一样。
- 内联函数在编译时用内联函数函数的函数体替换,所以不发生函数调用,不需要保护现场,恢复现场,节省了开销。
- 内联函数增加了目标程序的代码量。因此,一般只将函数规模很小且使用频繁的函数声明为内联函数。
- 当内联函数中实现过于复杂时,编译器会将它作为一个普通函数处理,所以内联函数内不能包含循环语句和switch语句。
内联函数格式如下:
inline 函数类型 函数名(形参列表){函数体;}inline int add(int a, int b){return a + b;}6.洞悉内联函数底层原理
1)使用Visual Studio 2015创建一个C++Win32控制台程序,点击项目->项目属性设置内联函数优化
2)编写内联函数代码,设置断点,debug启动
#include <iostream>#include <string>using namespace std;inline int add(int a, int b){return a + b;//断点1}int main(){int result = add(12, 34);cout << result << endl;//断点2return 0;}
3)调试->窗口->反汇编,然后就能看到编译后的汇编程序
...int result = add(12, 34);00B620DE mov eax,0Ch 00B620E3 add eax,22h //对eax中和22h中值进行相加,赋值给eax00B620E6 mov dword ptr [result],eax cout << result << endl;00B620E9 mov esi,esp 00B620EB push offset std::endl<char,std::char_traits<char> > (0B610A5h) 00B620F0 mov edi,esp 00B620F2 mov eax,dword ptr [result] 00B620F5 push eax 00B620F6 mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0B6D098h)] 00B620FC call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::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<char> >::operator<< (0B6D0ACh)] 00B62111 cmp esi,esp 00B62113 call __RTC_CheckEsp (0B611C7h) return 0;
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<char> > (02910A5h) 00291A64 mov edi,esp 00291A66 mov eax,dword ptr [result] 00291A69 push eax 00291A6A mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (029D098h)] cout << result << endl;00291A70 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::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<char> >::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;
从以上代码代码可以看出,在主函数中调用(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;}
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<char> > (01810A5h) 00181A64 mov edi,esp 00181A66 mov eax,dword ptr [result] 00181A69 push eax 00181A6A mov ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (018D098h)] cout << result << endl;00181A70 call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::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<char> >::operator<< (018D0ACh)] 00181A85 cmp esi,esp 00181A87 call __RTC_CheckEsp (01811C7h) return 0;00181AA3 xor eax,eax十.字符串(string)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;//输出
字符串的处理函数
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,否则返回-1strlen(char s[]);//计�����Խ�,��;֪��算字符串s的长度 字符串s的实际长度,不包括\0在内2.C++中的字符串(string)
字符串的定义和初始化
//定义string 变量;string str1;//赋值string str2 = "ShangHai";string str3 = str2;str3[3] = '2';//对某个字符赋值//字符串数组string 数组名[常量表达式]string arr[3];
字符串的处理函数
#include <iostream>#include <algorithm>#include <string>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个字符chinsert( 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函数如果未找到指定字符,返回的就是一个npositerator insert( iterator pos, CharT ch );iterator insert( const_iterator pos, CharT ch );void insert( iterator pos, size_type n, CharT ch );//迭代器指向的pos位置插入n个字符chiterator insert( const_iterator pos, size_type count, CharT ch );//迭代器指向的pos位置插入count个字符chvoid 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个字符,替换成字符串sstring& replace(size_t pos, size_t n, size_t n1, char c); //将当前字符串从pos索引开始的n个字符,替换成n1个字符cstring& 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);}11 .针和引用1.指针
指针是一个变量,其值为另一个变量的地址。即内存位置的直接地址。
声明的一般形式:
- 数据类型是指针变量所指向的变量的数据类型,*表示其后的变量为指针变量
数据类型 *指针变量名;int *ip; //整型的指针 double *dp; //double 型的指针 float *fp; //浮点型的指针 char *ch; //字符型的指针
指针变量的初始化:
- &是取地址运算符,&变量名表示变量的地址。
- 变量的数据类型必须于指针变量的数据类型一致。
- 为了安全起见,有时会把指针初始化为空指针(NULL或0)
数据类型 *指针变量名 = &变量名;*指针变量名 = &变量名;int a;int *p = &a;int *p2;p2 = &a;
指针变量的引用:
- & 取地址符 * 指针运算符(间接运算符),其后是指针变量,表示该指针变量所指向的变量。
- & *的优先级是相同的,结合方式都是自左向右。比如 &*p等价于&(*p)。
int x = 3;int y;int *p;p = &x;y = *p;//y = a
指针运算(地址运算)
- 算术运算(移动指针运算):加减,自增自减。
- p+n运算得到的地址是p+n*sizeof(数据类型)。
- 两个相同数据类型的指针可以进行加减运算,一般用于数组的操作中。
- 关系运算:指针指向同一串连续存储单元才有意义,比如数组。与0比较,判断是不是空指针。
- 赋值运算:变量地址赋值给指针变量,数组元素地址赋值给指针变量,指针变量赋值给其他指针变量。
int arr[10],len;int *p1 = &arr[2],*p2 = &arr[5]; len = p2-p1;//arr[2] 和arr[5]之间的元素个数 3
new和delete运算符
- new-为变量分配内存空间;
- 可以通过判断new返回的指针的值,判断空间是否分配成功。
- delete-释放空间
指针变量 = new 数据类型(初值);delete 指针变量;delete[] 指针变量;//释放为多个变量分配的地址int *ip;ip= new int(1);delete ip;int *ip; ip= new int[10]; for (int i = 0; i < 10;i++) { ip[i] = i; } delete[] ip;int a[3][4] = {0};
指针与数组
- 数组名是数组的首地址,eg:arr为arr[0]的地址。
- 访问数组元素:arr[i],(arr+i),(p+i),p[i]
- 二维数组:arr+i == &arr[i],arr[i] == &arr[i][0] ,*(arr[i]+j) == arr[i][j]
- 指针访问二维数组:指向二维数组元素,指向一维数组
- 数组指针:数据类型 (*指针变量名) [m]
int arr[10];int *p1 = arr;// *p1 = &arr[0];int a[3][5] = { 0 };int(*ap)[5];ap = a;ap+1;//表示下一个一维数组
指针与字符串
- 字符串数组名:
char ch[] = "heiren";char *p = ch;
- 字符串:
char *p = "heiren";
- 指针赋值运算:
char * p;p = "Heiren";
指针与函数,指针可以作为函数的参数,也可以作为函数的返回值。
引用可以看做是数据的一个别名,通过这个别名和原来的名字都能够找到这份数据,类似于window中的快捷方式。
- 引用不占内存空间,必须在定义的同时初始化,且不能再引用其他数据。
- 引用在定义时需要添加&,在使用时不能添加&,使用时添加&表示取地址
- 引用型变量声明:数据类型 &引用名 = 变量名;
int a;int &b = a;//a和b表示相同的变量,具有相同的地址。
引用可以作为函数参数,也可以作为函数返回值。
void swap(int &r1, int &r2) { int temp = r1; r1 = r2; r2 = temp;}int &add1(int &r) {r += 1;return r;}int main(){int a = 12;int b = add1(a);cout << a << " "<<b << endl;//13 13return 0;}
将引用作为函数返回值时不能返回局部数据的引用,因为当函数调用完成后局部数据就会被销毁。
函数在栈上运行,函数掉用完,后面的函数调用会覆盖之前函数的局部数据。
int &add1(int &r) {r += 1;int res = r;return res;}void test(){int xx = 123;int yy = 66;}int main(){int a = 12;int &b = add1(a);int &c = add1(a);test();//函数调用,覆盖之前函数的局部数据cout << a << " "<<b <<" "<< c<<endl;//14 -858993460 -858993460return 0;}未完待续~~~~·更多C++进阶教程等你领取!可以关注微信公众号 “C和C加加” 回复“ZXC”即可免费获取!