大一上第一学期期中复习知识点梳理 之 c++数组
一、数组
(一)基本概念
数组下标:下界从0开始,上界为数组元素个数-1。
(二)数组的定义与声明
定义:【修饰符】<数组元素类型><数组名><数组元素个数>【初值表】
数组元素类型除void不可。
int n=10; int a[n]; ❌
const int n=10; int a[n]; √
有初始化时,可以缺省数组元素个数。
数组初始化要求:可以给够给少,但不能给多。
初始化值的个数可以少于定义的元素个数,这时从第0个元素开始逐个取得初始化值,其余的元素初始化为0.
用字符串常量初始化时,字符数组会自动加上一个串结束符'\0',因此字符数组实际占用内存多一个字节存放'\0'字符。在定义字符数组大小时应注意留出保存串结束符的空间。
(三)操作
1、访问数组元素:
按下标访问。
注意:无越界警告!!! 若数组越界,会修改其他下标下数组值。
2、运算
全部初始化数组为0: a[]={0};
数组名作比较或者加减法或者输出,表示数组首地址(数字)操作。
特例:字符数组输出的元素内容。
3、函数传递
void func(int data[6]) 6没有任何意义,data表示的首地址,没办法判断data大小。
故func中void中size==4;main中size==3*4==12;
二、字符数组
(一)基础操作
字符数组输出字符的全部内容,直到遇到结束符才停止。(结束符不显示)
(二)库函数
(三)输入输出
cin>>若输入个数超出,则会发生错误超出部分保存于其他地方,安全做法为cin.get与getline。
get与getline可识别换行(空白)符。缓冲区中get到了换行符,那么存放到数组中存结束符,回车后内容不可再存放到数组中。重复再输入 方法1:cin.ignore();2:cin.get();
例如: 读取的输入样例为: A
abc
判断输入是否超出读取数(判断是否读到最后)(判断最后是否为换行符):用cin.getline时,用!cin或cin.good();判断最后是否为换行符,若不是的话此后还有并且还想继续输入,则应cin.clear();用cin.get时,则不会有之后能否继续读取的标记,推荐。
例如:
输入缓冲区:控制台中先加载到输入缓冲区,再从缓冲区中加载存放到变量中
数组核心:识别的量与下标对应关系理清
附/
char s[10]="ABC"; //此只为部分初始化,后面都为0;数组大小是由元素个数决定,不由内容决定。一个char类型1byte,故sizeof=10。
sizeof(s)=1*10=10;
sizeof看数组空间大小。
strlen只看内容,他会超出数组边界去找结束符。
例如:反转字符串
#include<iostream> using namespace std; int main() { char s[10]="ABC"; for(int i=0;i<strlen(s)/2;i++) swap(s[i],s[strlen(s)-1-i]); cout<<s<<endl; return 0; }
附/char s[3]={'a','b','c'};可,字符数组可有结束符可无结束符,无结束符就不为字符串。
字符数组可以存放字符串,但字符数组不一定就是字符串。
三、多维数组
(一)维数
(二)初始化
(三)操作
1、sizeof
作为形参传递时仍然传的首地址,func中的sizeof=4;main中的sizeof=4*3*3=36.
若在func中写入: data[1][1]=10;则main函数中data[1][1]值变化。
2、分层(分维)访问
3、运算
char a[][2];
a[1]-a[2] :首地址相减;a[1][1]-a[1][0]:ASCII码值相减。
四、数组基本应用
(一)统计
关键:找出数/字符与下标对应
(二)字符串处理
关键:注意下标间变换对应位置以及结束符对应移动
例题:
#include<iostream> #include<cstring> #include<cstdlib> using namespace std; char a[102],b[102]; char ug[102],unewn[102]; char g,newn; bool w_delete=false,w_replace=false; bool up_w_delete=false,up_w_replace=false; int length,ulength; int tot_delete=0,tot_replace=0; int up_tot_delete=0,up_tot_replace=0; int StringDelete(char g) { for(int i=0;i<length;i++) { if(a[i]==g) { if(!w_delete) w_delete=true;/*cout<<i<<endl;*/ for(int j=i;j<length-tot_delete;j++) a[j]=a[j+1];/*cout<<a<<endl;*/ tot_delete++;i--; } } if(w_delete) return tot_delete; return w_delete; } int StringReplace(char g,char newn) { for(int i=0;i<length;i++) { if(a[i]==g) { if(!w_replace) w_replace=true; a[i]=newn; /*cout<<i<<" "<<a<<endl;*/ tot_replace++; } } if(w_replace) return tot_replace; return w_replace; } int up_StringDelete(char ug[]) { for(int i=0;i<length;i++) { if(a[i]==ug[0]) { /*cout<<i<<" "<<a[i]<<endl;*/ int j; for(j=i;j<i+ulength;j++) { /*cout<<a[j]<<" "<<ug[j-i]<<endl;*/ if(a[j]!=ug[j-i]) {break;} } if(j==ulength+i) { up_w_delete=true; up_tot_delete++; for(int k=i;k<length;k++) { a[k]=a[k+ulength]; } /*cout<<a;*/ i--; } } } if(up_w_delete) return up_tot_delete; return up_w_delete; } int up_StringReplace(char ug[],char unewn[]) { for(int i=0;i<length;i++) { if(a[i]==ug[0]) { /*cout<<i<<" "<<a[i]<<endl;*/ int j; for(j=i;j<i+ulength;j++) { /*cout<<a[j]<<" "<<ug[j-i]<<endl;*/ if(a[j]!=ug[j-i]) {break;} } if(j==ulength+i) { up_w_replace=true; up_tot_replace++; for(int k=i;k<i+ulength;k++) { a[k]=unewn[k-i]; } /*cout<<a;*/ i--; } } } if(up_w_replace) return up_tot_replace; return up_w_replace; } int main() { cin.getline(a,101); /*cout<<a;*/ length=strlen(a); //字符操作部分 /*cin>>g;*/ //删除字符操作部分 /*cout<<"删除操作:"<<StringDelete(g)<<endl;*/ //替换字符操作部分 /*cin>>newn; cout<<"替换操作:"<<StringReplace(g,newn)<<endl;*/ //字符串操作部分 /*cin.getline(ug,101); ulength=strlen(ug);*/ //删除字符串操作部分 /*cout<<"删除操作:"<<up_StringDelete(ug)<<endl;*/ //替换字符串操作部分 /*cin.getline(unewn,101); cout<<"替换操作:"<<up_StringReplace(ug,unewn)<<endl;*/ return 0; }
(三)矩阵计算
本质:下标对应关系计算
例题:矩阵乘法
int a[3][4],b[4][5],c[3][5]; int i, j ,k; for(i=0;i<3;i++) for(j=0;j<5;j++) for(k=0;k<4;k++)] c[i][j]=a[i][k]*b[k][j];
标准板子:
#include <iostream> #include <iomanip> using namespace std; const int M=3, N=4, P=2; void matrix_mul( int x[M][P], int y[P][N], int z[M][N] ); int main() { int a[M][P] = {0,1,2,3}, b[P][N] ={{0,1},{2,3}}, c[M][N]; matrix_mul(a,b,c); cout<<"a["<<M<<"]["<<P<<"]="<<endl; for( int i=0; i<M; i++ ) { for( int j=0; j<P; j++ ) cout<<setw(4)<<a[i][j]; cout<<endl; } cout<<"b["<<P<<"]["<<N<<"]="<<endl; for( int i=0; i<P; i++ ) { for( int j=0; j<N; j++ ) cout<<setw(4)<<b[i][j]; cout<<endl; } cout<<"c["<<M<<"]["<<N<<"]="<<endl; for( int i=0; i<M; i++ ) { for( int j=0; j<N; j++ ) cout<<setw(4)<<c[i][j]; cout<<endl; } return 0; } void matrix_mul( int x[M][P], int y[P][N], int z[M][N] ) { int i,j,k; for( i=0; i<M; i++ ) { for( j=0; j<N; j++ ) { z[i][j]=0; for( k=0; k<P; k++ ) z[i][j] += x[i][k] * y[k][j]; } } }
(四)排列组合
1、八皇后问题
解决方案一:可用一维数组,下标表示行数,数组内容表示列数。依次检查是否满足对角线攻击可能性:相邻对应差值不能一样(45°)
本质:八个数字全排列遍历检查是否符合要求
问题:求n个数全排列:
想法一:先确定首位,再n-1个数排列。递归思想。
板子:
// 全排列示例 #include <iostream> using namespace std; void show( int array[], int num ); void permutate( int array[], int begin, int end ); #define N 4 int main( ) { int data[N], i; for( i = 0; i < N; i++ ) data[i] = i+1; permutate( data, 0, N-1 ); return 0; } void permutate( int array[], int begin, int end ) { int i; if( begin == end ) { show( array, end+1 ); return; } for( i = begin; i <= end; i++ ) { swap( array[begin], array[i] ); permutate( array, begin+1, end ); swap( array[begin], array[i] ); } return; } void show( int array[], int num ) { int i; for( i = 0; i < num; i++ ) { cout << array[i] << ' '; } cout << endl; return; }
想法二:字典序排列,保证后一个数比前一个数更大。开始最小的在前最大的在后,结束最小的在后最大的在前。大的往前提与在他前小的数交换,后面的数逆序。
板子:
八皇后板子:
#include <iostream> #include <ctime> #include <cmath> using namespace std; #define VER 1 #define N 8 int solution_num = 0; int call_num = 0; int check_num = 0; void Resolve( int array[], int num, int cur_index ); void ShowSolution( int array[], int num ) { static int k = 0; for( int i = 0; i < num; i++ ) { cout << array[i]; } if( ++k % 5 == 0 ) cout << endl; else cout << ' '; return; } int main() { int queen_pos[N]; for( int i = 0; i < N; i++ ) queen_pos[i] = i; // int begin_tm = clock(); Resolve( queen_pos, N, 0 ); // int pass_tm = clock()-begin_tm; cout << "\nVER " << VER << endl; cout << "total solutions: " << solution_num << endl; cout << "call/loop times: " << call_num << endl; cout << "check times: " << check_num << endl; // cout << "time: " << pass_tm << endl; return 0; } #if VER == 1 bool FullCheck( int array[], int num ) { bool result = true; check_num++; for( int i = 0; i < num && result; i++ ) { for( int j = i+1; j < num && result; j++ ) if( abs(array[i] - array[j]) == j - i ) result = false; } return result; } // 递归全排列 void Resolve( int array[], int num, int cur_index ) { int i; call_num++; if( cur_index == num-1 ) { if( FullCheck( array, num ) ) { solution_num++; ShowSolution( array, num ); } } else { for( i = cur_index;i < num;i++ ) { swap(array[cur_index],array[i]); Resolve(array,num,cur_index+1); swap(array[cur_index],array[i]); } } return; } #endif #if VER == 2 bool Check( int array[], int cur, int k ) { check_num++; for(int i = 0;i < cur; i++ ) { if( array[i] == k || abs(array[i] - k) == cur - i ) return false; } return true; } // 递归回溯 void Resolve( int array[], int num, int cur_index ) { call_num++; if( cur_index >= num ) { ShowSolution( array, num ); solution_num++; return; } for(int i = 0;i < num; i++) { if( cur_index == 0 || Check( array, cur_index,i)) { array[cur_index] = i; Resolve( array, num, cur_index+1); } } return; } #endif #if VER == 3 int Choose( int array[], int num, int cur_index, bool sel[] ) { check_num++; for( int i = cur_index; i < num; i++ ) { int pos = array[i]; if( !sel[pos] ) { for( int j = 0; j < cur_index; j++ ) if( abs(array[j] - pos) == cur_index - j ) break; if( j == cur_index ) { sel[pos] = true; return i; } } } return -1; } // 递归回溯+排列选择 void Resolve( int array[], int num, int cur_index ) { call_num++; if( cur_index >= num ) { ShowSolution( array, num ); solution_num++; return; } bool selected[N] = {0}; for(int i = 0;i < num - cur_index; i++) { int pos = Choose( array, num, cur_index, selected ); if( pos != -1 ) { swap( array[cur_index], array[pos]); Resolve( array, num, cur_index+1); } } return; } #endif #if VER == 4 int status[N][N+1]; void RemoveAttack( int array[], int num, int cur_index, int end_index, int pos ) { for( int i = cur_index+1, k = 1; i <= end_index; i++, k++ ) { if( --status[i][pos] == 0 ) status[i][num]--; if( pos + k < num && --status[i][pos+k] == 0 ) status[i][num]--; if( pos >= k && --status[i][pos-k] == 0 ) status[i][num]--; } return; } bool Attack( int array[], int num, int cur_index, int pos ) { check_num++; for( int i = cur_index+1, k = 1; i < num; i++, k++ ) { if( status[i][pos]++ == 0 ) status[i][num]++; if( pos + k < num && status[i][pos+k]++ == 0 ) status[i][num]++; if( pos >= k && status[i][pos-k]++ == 0 ) status[i][num]++; if( status[i][num] == num ) // all positions are attacked break; } if( i != num ) // need roll back { RemoveAttack( array, num, cur_index, i, pos ); return false; } else return true; } // 循环回溯+攻击标志 void Resolve( int array[], int num, int cur_index ) { call_num++; int i; for( i = 0;i < num; i++) { array[i] = -1; } for( i = 0;i <= num && i >= 0; ) { // choose a position not under attack call_num++; int pos = -1; for( int j = array[i]+1; j < num; j++ ) { if( status[i][j] == 0 ) { pos = j; break; } } if( pos != -1 ) // find a position { array[i] = pos; if( i == num - 1 ) // find a solution { ShowSolution( array, num ); solution_num++; } else { bool is_ok = Attack( array, num, i, pos ); if( is_ok ) // attack test ok { i++; } continue; } } // go back array[i] = -1; i--; RemoveAttack( array, num, i, num-1, array[i] ); } return; } #endif
(五)排序、查找
1、顺序查找
2、对半查找
基本思想:把数据一分为二,不断二分。
#include <iostream> #include <iomanip> using namespace std; #define VER 2 int BinarySearch( int data[], int num, int key ); void ShowRange( int data[], int num ) { static int lv = 0; cout << ++lv << ")\t"; for( int i = 0; i < num; i++ ) { cout << setw(2) << data[i] << ' '; } cout << endl; } int main() { int data[] = { 4, 9, 11, 16, 25, 33, 47, 62 }, key; cout << "Input the key to search:" << endl; cin >> key; int index = BinarySearch( data, sizeof(data)/sizeof(int), key ); if( index != -1 ) cout << "Find " << key << " @"<< index << endl; else cout << "Key " << key << " Not Found!" << endl; return 0; } #if VER == 1 // 递归 int BinarySearch( int data[], int num, int key ) { ShowRange( data, num ); if( num <= 0 ) return -1; else if( num == 1 ) return key == data[0] ? 0 : -1; int mid = num/2; bool search_dir = data[mid] > data[0] ? key < data[mid] : key > data[mid]; int offset, start; if( search_dir ) // 前半区 { start = 0; num = mid; } else // 后半区 { start = mid; num -= mid; } offset = BinarySearch( &data[start], num, key ); return (offset == -1 ? -1 : start + offset); } #endif #if VER == 2 // 循环 int BinarySearch( int data[], int num, int key ) { int start = 0; while( num > 1 ) { ShowRange( &data[start], num ); int mid = start + num/2; bool search_dir = data[mid] > data[0] ? key < data[mid] : key > data[mid]; if( search_dir ) // 前半区 { num = mid - start; } else // 后半区 { num -= ( mid - start ); start = mid; } } ShowRange( &data[start], num ); return key == data[start] ? start : -1; } #endif
3、直接查找
散列表(稀疏数据集合)
例如,对数据表中一系列数进行特定操作(如取余某个数)使每个数转换成有限范围内尽可能唯一结果,把一系列数放在一个有限的数组中,只需进行一次比较即可得到。
但若两个数据操作后可能会哈希冲突,则可使用链表多次比较。
4、分类查找
常用于复杂问题中
五、指针与地址
(一)概念
描述性地址:地址在哪,存放的数据类型是什么。
指针类型区分:按它指向的变量的类型区分。
明确指针指向哪种数据类型:指针中需放变量首地址,要知道占多少内存,数据怎样组织。
指针变量存放的是在内存中可寻址的变量或对象的首地址,只能取一个已经分配了内存的变量的地址赋给指针变量。
&:取地址运算符。获得地址。不能作为左值。
*:间接引用运算符。访问指针所指向的内存数据。作为左值。
&的多重用法:
cout<<&a; 中&:取地址运算符号
int &b=a 中 &:在定义中出现 ,为引用类型标志
a&b 中&:位与运算符
定义中或其前出现类型时出现符号为类型标志,表达式运算中符号为运算符
类似:[]的多重用法:int s[2] 中 []:数组类型标志 ; s[1]=0 中[]:下标运算符。
int i; char c;
cout<<&i; :输出i地址; cout<<&c; :按字符串输出。
(二)定义
指针变量定义时要加“*”(这里*位说明符)。
在可执行语句中:p表示指针本身时,不用也不允许加“*”;“*”称为间接引用运算符,*p代表p所指向的内存中可寻址的数据。
第一句意义:0地址下存放char类型table值
const char*str; const指向内容,表内容只读不写 ,地址可改变。
char * const s:const指向本身,修饰s指针本身,表示地址不可变,内容可改变。
指针必须赋给一个有效地址才能引用,即必须初始化。
指针可以指向数据大小不知道的void,而数组不可以指向未知大小的void。
char *str="Hi"; 指向"Hi\0"字符串首地址("Hi")。且注意:“Hi"字符串必须用相应char类型。
const存的字符串首地址,只读不改;单char存的数组首地址,可以修改。
空字符串:字符串ASCII码为0;空指针:地址为0.
typedef int定义指针,简化指针定义表达。
(三)操作
数组名是指针常量。不可进行运算操作。
0地址不可访问内容。
*q='a'; 等价于 b='a';
*p=*q+1;等价于 a=b+1;
q=p; p、q地址都指向a;
*多重含义:
char *s;:指针标识
cout<<*a; :取指针内容
a*b;:乘法运算
指针有算术运算和关系运算,只有当指针是指向数组元素时这些运算才有意义。规则:
1)指针变量与整型变量n的加减表示移动指针: 新指针值=原指针值+n*sizeof(指针指向的数据类型)。
新的地址是原地址后第n个元素的地址,即指针移动n个元素的位置。
注意:此不对数组边界进行检查。
2)只有当两个同类型的指针变量指向同一个数组时才可以进行减法运算,结果表示由第一个指针所指元素到第二个指针所指元素之间的元素数量。
例如:第1个和第2个元素之间的元素数为1,即两指针地址值的差除以sizeof(数组元素类型)。
3)当且仅当两个同类型的指针变量指向同一数组中的元素时,可以用关系运算符">""==""!="等比较。比较规则:指向后面元素的指针大,指向同一元素的指针相等。
注意:两个指针相加毫无意义。
故建立两个指针间的指针,不能写成p=(p1+p2)/2,必须写成p=p1+(p2-p1)/2。
4)指针同样可以进行"++""--"运算,结果也是指向后一个或前一个数组元素。
注意:“++”优先级高于“*”,故p=*q++等价于p=*(q++).
*p++:先地址++再取*内容 例如:s[0]变成s[1];若想对内容操作:(*p)++; 例如:s[0]内容+1;
附/此main中p赋值有风险:把func中局部变量值传给p,局部函数结束生命期已经结束,可能会随意篡改其中(其他函数)内容。
引用与指针的差别:
引用可以直接写,不用间接访问;传参直接传;必须有效定义好的变量才可传入,有实体。
int * func(int &b){ b=3; return b;}
//若{int c=3; return c;}不可!
int main()
{int a; int c=func(a)+10;}
注意:对局部变量不能返回引用不能返回指针!!!
指针要间接引用;传参传地址;更灵活,可变化不同数据地址,可无实体。
int main()
{int a[3]; func(a);传数组首地址}
int func(int *b等价于int b[])
{*b=1;*b++=2;b++;*b=3;} 含义:a[0]=1;a[1]=2;从a[1]指向a[2];a[2]=3。
原有约束不可以在再赋给别人后放宽约束,无约束的可在新赋给别人时有约束。
放前:c内容为'A‘不可修改,把c给p表示p内容不变,但是可以变换指针指向。 放后:q只能指向d,不能再改地址,但可以改内容。
1、× *p可赋值给别人,但自身内容不能改变;2、√ d=’D'; 3、编译不错运行错,改的常量;4、× s可改内容,p不可改内容,不可,这样会放松p约束条件。5、√ 约束更严格 6、× q地址不变 7、√ 8、× 9、√ 更严格约束 10、√此时s指向q,q指向d,把d改成‘E',可以。
char *s="OK",语法不报错但不合理,应该const char.
(四)
char *strcpy(char *dest,const char *src) { const char *p=src; char q=dest; while(*p!='\0') { *q++=*p++; //后增量运算,先赋值再自增。 //或 *q=*p; p++;q++; } } int strlen(const char *str) { const char *p=src; int len=0; while(*p++!='\0') len++; return len;} int main() { char s[10],t[]="hello"; strcpy(s,t); }
数组:开头只能为首地址偏移;指针:可指向其中任何一位置为起始位置进行后续操作偏移。
例如:等价于 const char *p=*&t[0];
q=&data[0] ;q=data;一样,指向首地址。
int*q;q=&s[2]; 则q[2]=s[4];
sizeof(data)=4*5=20; sizeof(p)=4;指针只保存地址。
访问具体值: int *p; p=&data[0][1]; 指针指向:正确地址,类型相同。 p[2]==data[1][0];
int (*q)[3]相当于定义q为一维数组,q[0]==data[0].
q[1][1]==data[1][1];
cout<<table[1]; 字符指针按字符串输出内容,其他输出地址。
sizeof(table)=4*2=8;\
*p[2]为存放指针的数组,(*p)[2]为指向数组的指针。
函数指针:
(五)
*p=data; **q=&p. **q 二级指针。 *q取出*p,*(*q)再获得data,简写为**q。
p[1]为long*指针。指针可进行下标操作。p[1][1]等价于long*指针+1,从data[1][0]加至data[1][1]。
*p为long*类,*p+1:data[0][0]->data[0][1]。 *(p+1)指向第二行首地址,long*。**(p+1)取内容data[1][0].
long **q=data;(×) 左:long *(*q)为long*型,data为long [2]型,两者不匹配。 long (*s)[2]=data.(√).
long (*s)[2]=data.
*s+1(×). *(s+1)==data[0][1] ,s[0][1]==data[0][1].s行数可不定,列数一定。 sizeof(*s)==2*4=8.???
p[1][1]==data[1][1],q[1]==p[2],q为指针指首地址,指针下标操作。 q[1][0]==data[1][0]. q[0]+1==p[0](long*)+1==&data[0][1]. *(q+1)==p[1]等价于q[1].
**(q+1)==data[1][0]. *(*q+1)==data[0][1]. *q+1==&data[0][1]. sizeof(*q)==4;sizeof(**q)==sizeof(*p)==4???? ; *p==*(p+0)==p[0].
q[0][1]==data[0][1],需通过p(存放指针的数组)中转
多级指针:行列都不确定;数组指针:行不定列确定;多维数组:行列都确定。
(六)索引查找与指针数组
(七)string字符串
getline(流对象,数据名,结束符).全局
下标运算只能访问已有有效数据,不访问结束符。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下