C/C++语言基础回顾
C语言
基本数据类型与运算表达式
- 整型
#include <stdio.h>
#include <stdlib.h> //在预处理时循环展开
//define常量在预处理为main.i文件时就已全部替换
#define PI 3+2
int main() {
//vs要求定义语句都放在前面
int a = PI * PI;
int b = 10;
int c = 012;
int d = 0xA;
printf("a = %d",a);
system("pause");
}
- 补码
#include <stdio.h>
#include <stdlib.h>
int main() {
int i = -5;
int j;
j = i + 2;
//unsigned 无符号 默认缺省 signed 有符号
unsigned int k = 0xffffffff;
printf("j = %d\n",j);
//%u以无符号整型输出
printf("k = %u\n",k);
system("pause");
}
- 溢出
#include <stdio.h>
#include <stdlib.h>
int main() {
short a,b;
//有符号 short int型可表示最大正整数为32767
a = 32767;
b = a +1;
//发生数据溢出
printf("b = %d\n",b);
system("pause");
}
- 浮点数
#include <stdio.h>
#include <stdlib.h>
int main(){
float i = 3e-3;//e 为10的幂次
//符号位1位 指数位 8位 尾数位 23位
float f = 1.456;//浮点数是近似值是由于指数位只能用2的幂次来近似某些数值后再与尾数位相乘
//所以判断时不能使用等号判断
float a= 1.23456789e10;//float有效数字为7位
float b;
b= a+20;
printf("b= %f\n",b);//发生精度丢失
if (f==1.456){
printf("f is equal to 1.456 \n");
}else {
printf("f is not equal to 1.456\n");
}
//要使用是否在精度范围内来判断是否相等
if (f-1.456>-1e-7&&f-1.456<1e-7){
printf("f is equal to 1.456 \n");
}else {
printf("f is not equal to 1.456\n");
}
system("pause");
}
- 字符型
#include <stdio.h>
#include <stdlib.h>
int main() {
char a = 'A';
char b = 65;
char c = '\0';//空字符用于表示字符串的结束 相当char c = 0;
char *p = "abc";//字符串常量与整型,浮点型常量不同,她是被存储在数据区的常量区中,整型常量是从数据区移动到栈区赋值给变量
char *p2 = "abc";
char d = a+32;//大写转小写字母
//判断是否为大写字母
if(a>='A'&&a<='Z'){
printf("变量a为大写字母");
}
printf("a= %c",a);
printf("a= %d",a);//在内存中对应存储的值为65,经查表打印字符A
printf("abc\b");// \b为光标后退一格并不会删除
printf("abc\b "); //后退一格再输出空格实现将c删除的效果
printf("abc\rd");// \r回到行首
system("pause");
}
- 混合运算
#include <stdio.h>
#include <stdlib.h>
int main(){
float f;
int i = 5;
f = i/2; //i/2结果为整型2再转成float类型2.0
long l = 5;
short s;
s = l+1; //会出现警告,通过(short)l 强转解决
system("pause");
}
- scanf
#include <stdio.h>
#include <stdlib.h>
int main() {
int i,j;
char c;
//缓冲区:在内核区的一段内存空间
//全缓冲:填满I/O 磁盘读写 行缓冲:遇到换行符 stdin stdout 不带缓冲 立马输出 stderr
scanf("%d",&i);//scanf为阻塞的
printf("i=%d",i);
//%d %f 在scanf时忽略\n和空格 %c不忽略
//scanf("j=%d,&j");
//printf("j=%d,j");
scanf("%c",c);//希望阻塞
printf("c=%c",c);//换行符在标准输入缓冲区不会在读取后删除,在这次scanf时读取了/n
system("pause");
}
- while_scanf
#include <stdio.h>
#include <stdlib.h>
//标准输入缓冲区原理 stdin
//fflush(stdin) 清空标准输入 rewind
int main() {
int i,j;
int ret;
ret = scanf("%d%d",&i,&j); //scanf 返回值为匹配成功的个数
//若不清空标准输入缓冲区,则下次错误输入字符a时无法匹配,将会出现循环输出上次缓冲区中的内容
//scanf("%d",input);这句语句从缓冲区接受数字,而当我们输入字母或其他时,字符就一直留在缓冲区,循环第二次时,scanf再次从缓冲区获取时,还是不是数字,就相当跳过了scanf这一句......从而,造成死循环。
//如何解决呢?
//1.在scanf这一句后面加一句getchar(); 用于接受字符;
//2.在scanf这一句前面加fflush(stdin); 这一函数用于清空缓冲区,但他并不适用于所有编译器,遇到vs2015,gcc编译器时,它就失效了
while(fflush(stdin),scanf("%d",&i)!=EOF){
printf("i=%d \n",i);
}
system("pause");
}
- scanf读取字符串
#include <stdio.h>
#include <stdlib.h>
int main() {
char c;
while(scanf("%c",&c)!=EOF){
printf("%c",c); //不用加\n,因为输入结束时的回车被存储在缓冲区中,也不必使用fflush
}
system("pause");
}
- scanf多种类型混合输入
#include <stdio.h>
#include <stdlib.h>
int main() {
int i;
float f;
double d;
char c;
int ret;
ret = scanf("%i%f%lf %c",&i,&f,&d,&c);//在%c前添加一个空格,模式匹配为了%c忽略用来间隔的空格,double类型在输出的时候可以用%f,但在输入的时候要用%lf
printf("%i%f%f%c\n",i,f,d,c);
system("pause");
}
- 算术运算符
#include <stdio.h>
#include <stdlib.h>
int main() {
int i,k;
int j = 0;
scanf("%d",&i);
//判断是否为对称数
while(i!=0){
k = i%10;
printf("%d\n",k);
j = j*10+k;
i = i/10;
}
printf("%d\n",j);
system("pause");
}
- 逻辑运算符和逻辑表达式
#include <stdio.h>
#include <stdlib.h>
int main() {
int year;
int i = 0;
while(scanf("%d",&year)!=EOF){
//运算符优先级
if(year%4==0&&year%100!=0||year%400==0){
printf("%d is leap year\n",year);
}else {
printf("%d is not leap year\n",year);
}
}
//短路与和短路或
i == 0&&printf("system is error\n");//等于0的时候打印
i == 0||printf("system is error\n");//不等于0的时候打印
system("pause");
}
- 位运算符
#include <stdio.h>
#include <stdlib.h>
int main() {
short s = 0x10;
// << 高位丢弃,低位补0 相当于乘2 移位器效率高
// >> 右移 低位丢弃 正数高位补0 负数高位补1 正数相当于除以2,低位舍弃
//如果是负数 >> 右移 需要先加1再右移 达到除以2,低位舍弃的效果
s = s<<1;
printf("s = %d",s);
s = 0xf0f7;
s = ~s;
printf("negetion s = %x",s);
//1.一个数跟自己异或结果为0
//2.一个数跟0异或结果是自己
//3.按位异或满足交换律
printf("5&7",5&7);//值为5 都为1才为1
printf("5|7",5&7);//值为7 都为0才为0
printf("5^7",5&7);//值为7 位异或0则不变 位异或1则取反
system("pause");
}
- 找出出现一次的那个数
#include <stdio.h>
#include <stdlib.h>
#define N 5
int main() {
int a[N] = {6,9,7,9,6};
int result = 0;
int i;
//利用异或性质找出只出现一次的那个数
for(i=0;i<N;i++){
result = result^a[i];
}
printf("result = %d\n",result);
//利用按位与,判断一个数是不是2的幂次,a&a-1 = 0
system("pause");
//若一些数中只有两个数出现一次,其他数均出现两次,请找出那个数
//首先分堆 将所有数进行异或运算得到的结果为要寻找的那两个数各位的相同情况
//选取这两个数不同的那一位并设为1其他位均设为0,与所有数进行按位与,即可通过结果分为两堆
//且两堆各自包含一个所求数,其他数均为成对出现,此时每堆所有数进行按位异或即可找出结果
}
- 找出两个出现一次的数
#include <stdio.h>
#include <stdlib.h>
#define N 8
int main() {
short a[N] = {5,6,9,11,7,9,6,5};
short result =0;
short obj1=0,obj2=0;
int i;
for(i=0;i<N;i++){
result = result^a[i];
}
// a&(-a)可找出此数a最低位为1的那个数,根据负数补码特性
result = result&-1*result;
for(i=0;i<N;i++){
if(a[i]&result){
obj1 = obj1^a[i];
}else{
obj2 = obj2^a[i];
}
}
printf("obj1=%d obj2=%d",obj1,obj2);
system("pause");
//如果在103个数中找3个出现一次的数
//让数组中所有的数与0000 0001进行按位与,分成两堆,想要分成其中一堆存在两个目标值,一堆存在一个目标值
//再让这两堆各自所有数进行异或操作,如果其中有一堆的值等于0,则目标数的最低位相同,三个目标值都存在一堆之中
//这时让所有数与0000 0010进行按位与,重复操作直至两堆各自异或值都不等于0,此时奇数个堆存在存在一个目标值即异或的结果,偶数个堆存在两个目标值,继续通过找两个出现一次的数的方法进行分堆处理
}
- 条件运算符
#include <stdio.h>
#include <stdlib.h>
int main() {
int i,j,k;
int ret;
//根据三目运算符判断最大值
//while(scanf("%d%d%d",&i,&j,&k)!=EOF) {
// ret = i>j?(i>k?i:k):(j>k?j:k); //或者 (i>j?i:j)>k?(i>j?i:j):k
// printf("max = %d\n",ret);
//}
//利用三目运算符实现正数负数除以2的操作 关于取模a%b=a-(a/b)*b
//while(scanf("%d",&k)!=EOF){
// ret = k>=0?k>>1:k+1>>1;
// printf("k/2=%d\n",ret);
//}
//如果5,6,7不括起来k = 5,因为赋值运算符优先级高于逗号运算符
k = (5,6,7);//逗号表达式整体的值是最后一个表达式的值 例如while(fflush(stdin),scanf("%d",&i))
system("pause");
}
- 自增自减
#include <stdio.h>
#include <stdlib.h>
int main() {
int i = -1;
int j;
j = i++ > -1; //分成两步走, j=i>1; i=i+1
printf("i=%d, j=%d",i,j);
j = !++i;
printf("i=%d, j=%d",i,j); //同优先级单目运算符结合顺序从右至左
system("pause");
}
循环与分支语句
- if与else
#include <stdio.h>
#include <stdlib.h>
int main() {
int i;
while(fflush(stdin),scanf("%d,&i") != EOF){
if (i>0) {
printf("%d is bigger than zero");
}else {
printf("%d is not bigger than zero");
}
}
system("pause");
}
- switch
#include <stdio.h>
#include <stdlib.h>
int main() {
int year, month;
while(scanf("%d%d",&year,&month)!=EOF){
switch(month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
printf("%dmonth is 31 days",month);
break;
case 4:
case 6:
case 9:
case 11:
printf("%dmonth is 30 days",month);
break;
case 2:
printf("%dmonth is %d days",month,28+(year%4==0&&year%100!=0||year%400==0));
break;
default:
printf("err month");
}
system("pause");
}
}
- goto无条件转移语句
#include <stdio.h>
#include <stdlib.h>
void print() {
label:
printf("I am print func"); //goto是不可以跳转到其他函数的
}
int main() {
int i = 1;
int total = 0;
label:
total = total + i;
i++;
if(i<=100) {
goto label; //向上跳转实现循环
}
printf("tatal = %d",total);
system("pause");
}
- while循环
#include <stdio.h>
#include <stdlib.h>
int main() {
int i =1;
int total = 0;
while(i<=100) {
total = total +i;
i++; //是表达式趋近于假
}
printf("total = %d",total);
system("pause");
}
- for循环
#include <stdio.h>
#include <stdlib.h>
int main() {
int i,total;
for(i=1,total=0;i<=100;i++){
total = total +i;
}
printf("total = %d",total);
/*i =1, total=0;
for(;;){
if(i<=100){
total = total +i;
}else {
break; //结束整个循环
}
i++;
} */
//i =1, total=0;
//for(;i<=100;i++){
// if (i%2==1){
// continue; //跳过循环体下面未执行的语句
// }
// total = total +i;
//}
system("pause");
}
- 菱形打印
#include <stdio.h>
#include <stdlib.h>
int main() {
int i,j;
for(i=0;i<9;i++){ //外层控制打印几行
for(j=0;j<abs(4-i);j++){ //找出每行前面打印空格的规律
printf(" "); //abs也可以用(4-i>0?4-i:i-4)代替
}
for(j=0;j<9-2*abs(4-i);j++){ //内层控制打印什么内容
if(j%2==0){ //根据数学规律和图形的对称性来设计代码
printf("*");
}else {
printf(" ");
}
}
printf("\n");
}
system("pause");
}
- 日期计算
#include <stdio.h>
#include <stdlib.h>
int main() {
int a[] = {31,28,31,30,31,30,31,31,30,31,30,31};
int year,month,day;
int i;
while(scanf("%d%d%d",&year,&month,&day)!=EOF){
int total = 0;
for (i=0;i<month-1;i++){
total = total + a[i];
}
total = total + day;
if (month>2) {
total = total + (year%4==0&&year%100!=0||year%400==0);
}
printf("%04d-%02d-%02d is %dth day",year,month,day,total);
}
//输入两个日期计算相差多少天 sum = delt(year1,year2) - dayofyear(year1) + dayofyear(year2) //delt函数为计算两个年份的1月1号相差多少天
//输入一个日期计算今天星期几 向后计算 delt%7 向前计算 (7-delt%7)%7 delt为计算与已知为星期天的日期相差的天数
//输入一个日期和一个整数n,计算从该日期起经n天以后的日期 ->date-> tomorrow of the date
system("pause");
}
数组与指针
- 一维数组
#include <stdio.h>
#include <stdlib.h>
#define N 5
void print(int a[],int len) { //a和len都是形参
int i;
for(i=0;i<len;i++){
printf("a[%d] = %d",i,a[i]);
}
}
int main() {
// int i = 10;
// int a[i]; //定义时a[]内不能使用变量
// int a[0]; //不能为0,没有意义
//一个函数的函数栈1M 在栈中定义的数组有大小限制
int a[N]={1,2,3,4,5};
int b[5]={0};//剩余元素默认都是0
// b = {1,2,3,4,5} 数组声明后不能再对b进行赋值,因为此时的b已经是一个常量地址确定下来
//数组访问越界
//a[5] = 6;
print(a,N); //函数调用是值传递,传递的是数组a的地址,是指针类型,大小是4个字节
//实参a,N的值传递给了形参a,len
//一维数组的传递,无法将长度传递过去
//函数执行就是压栈弹栈的过程
system("pause");
}
- 二维数组
#include <stdio.h>
#include <stdlib.h>
//void print(int a[],int len) { //a和len都是形参
// int i;
// for(i=0;i<N;i++){
// printf("a[%d] = %d",i,a[i]);
// }
//
//}
void print(int a[][4],int row) { //这里的int a[][4] 是由于二维数组弱化成了数组指针进行传递
int i,j;
for(i=0;i<row;i++){
for(j=0;j<sizeof(a[0])/sizeof(int);j++){
printf("%3d",a[i][j]);
}
printf("\n");
}
}
int main() {
int a[3][4] = {1,3,5,7,2,4,6,8,9,11,13,15};
//print(a[0],4); //a[0],a[1],a[2] 分别是一个一维数组
print(a,3);//二维数组无法传递有几排
system("pause");
}
- 字符数组
#include <stdio.h>
#include <stdlib.h>
void print(char c[]) {
int i = 0;
while(c[i] != 0) { //0为\0的ASC编码 可简写成 while(c[i]){}
printf("c[i]");
i++;
}
printf("\n");
}
int main(){
//char c[10] = {'1','2','3','4','5','6','7','8','9','0'};
char c[10] = "123456789"; //字符串长度最多写九位要保留最后一位为\0结束符,否则会一直向下输出字符直到遇到\0
printf("%s",c);
print(c);
char a[10];
//scanf %s 会忽略\n和空格
scanf("%s%s",a,c);
printf("%s",a,c);
system("pause");
}
- gets和puts
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
size_t mystrlen(char c[]) {
int i;
while(c[i]){
i++;
}
return i;
}
//str系列
// size_t strlen(char *str) 统计字符串长度
// char *strcopy(char *to,const char *from) 将某字符串复制到字符数组中
// int strcmp(const char *str1,const char *str2) 比较两个字符串的大小
// char *strcat(char *str1, const char *str2) 将两个字符串连接到一起
//strn系列
int main() {
char c[20];
gets(c);//遇到 \n 或EOF才会停止从stdin中读取字符 也就是一次读取一行
//gets还会消除\n
while(gets(c)!=NULL){ //gets的返回值是指针类型,所以=NULL时结束
//NULL = 0 所以可以简写成while(gets(c)){}
//printf("%s\n",c);
puts(c);//打印字符串同时并多输出一个\n
}
printf("%s",c);
system("pause");
}
- mem系列
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int a[5] ={1,2,3,4,5};
//多用于清空数组
int b[5];
int ret;
memcpy(b,a,sizeof(a));
ret = memcmp(a,b,sizeof(a));
memset(a,0,sizeof(a)); //0表示指定填充的内容 sizeof(a)表示要设置的字节大小
}
- 一级指针
#include <stdio.h>
#include <stdlib.h>
int main() {
int i=10;
printf("i=%d",i); //直接访问
int *p=&i;
//&*p与p是等价的
printf("*p=%d",*p); //间接访问
system("pause");
}
- 指针的传递
#include <stdio.h>
#include <stdlib.h>
void change(int *i) {
*i=5; //间接访问拿到变量i
}
int main() {
//指针的传递实现调用函数改变被调用函数的值
int i = 10;
printf("before change i=%d",i);
change(&i); //传递变量i的地址
printf("after change i=%d",i);
}
- 指针的偏移
#include <stdio.h>
#include <stdlib.h>
#define N 5
int main() {
int a[N] = {1,2,3,4,5};
int *p = a;
int i;
for(i=0;i<N;i++){
printf("%3d",*(p+i)); //p[i]等价于*(p+i) p+1表示的是 p的值+sizeof(p所指向的数据的类型大小) 为了取下一个元素
}
p=&a[5];
//逆序输出
for(i=0;i<N;i++){
printf("%3d",*(p-i));
}
system("pause");
}
- 指针的自增自减
#include <stdio.h>
#include <stdlib.h>
int main() {
int a[3]={2,7,8};
int *p;
int j;
p=a;
//分两步,第一步直接去掉后++,后一步怎么结合要看优先级
j=*p++; //首先去掉++运算符变为为j=*p 再看是(*p)++还是p++,只有第一类运算符才能当作整体
//即拆成j=*p; p=p+1;
printf("a[0]=%d,j=%d,*p=%d",a[0],j,*p); //2,2,7
j=p[0]++; //j=p[0]; p[0] = p[0] +1
printf("a[0]=%d,j=%d,*p=%d",a[0],j,*p); //2,2,8
system("pause");
}
- malloc动态内存申请
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
int i;
char *p;
scanf("d",&i);
//一旦申请,一直存在,除非free
p =(char *) malloc(i);
//p="hello" 若这样赋值存储的是hello字符串常量的地址,并不是将字符串拷贝到申请的堆空间中
strcpy(p,"hello");
puts(p);
//内存释放时要使用malloc返回的地址,不能偏移,可定义一个新的变量进行偏移
free(p);
system("pause");
}
- 栈与堆的申请
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//栈空间申请一次就给1M空间,通过栈顶指针进行管理
//堆空间申请一次就要进行虚拟地址到物理地址的映射
//栈空间函数执行结束后会弹栈,这片内存会释放提供给其他函数使用
char *print_stack() {
char c[]="I am print_stack";
puts(c);
return c;
}
//堆空间一旦申请,一直存在,除非free
char *print_malloc (){
char *p;
p=(char *)malloc(20);
strcpy(p,"I am print_stack");
puts(p);
return p;
}
int main() {
char *p;
//p=print_stack();
p=print_malloc();
//可在不同函数中malloc和free
free(p);
puts(p);
system("pause");
}
- 野指针
#include <stdio.h>
#include <stdlib.h>
//什么是野指针,free以后的指针未被赋值为NULL
int main() {
int *p,*p1,*p2;
p=(int *)malloc(4);
*p=1;
p1=(int *)malloc(4);
free(p);
//未提高效率内部缓存机制并未直接将空间还给操作系统,而是将刚刚free的空间提供给这次申请使用
//所以free的指针务必将其赋值为NULL,否则会造成很多不易排查的错误
//NULL为0x00000000这个地址不映射任何地址空间
p2=(int *)malloc(4);
*p2=3;
*p=20;
printf("*p2=%d\n",*p2);
system("pause");
}
字符指针、数组指针、函数指针
- 字符指针与字符数组的初始化
#include <stdio.h>
#include <stdlib.h>
int main() {
char *p = "hello"; //p存储的是数据段字符串常量区hello的起始地址
char c[10] = "hello";//相当于strcpy,将hello拷贝到栈空间
c[0] = 'H';
//p[0] = 'H'; 不合法,字符串常量区数据不可修改
printf("%s",p);
printf("%s",c);
p = "world";
//c = "world"; c此时为一个地址常量不可修改
printf("%s",p);
printf("%s",c);
system("pause");
}
- 数组指针与二维数组
#include <stdio.h>
#include <stdlib.h>
//数组指针多服务于二维数组
void print(int (*p)[4],int row) {
int i,j;
for(i=0;i<row;i++){
for(j=0;j<sizeof(*p)/sizeof(int);j++){
printf("%3d",*(*(p+i)+j)); //相当于p[i][j ]
}
}
}
int main() {
int a[3][4] = {1,3,5,7,2,4,6,8,9,11,13,15};
int b[4] = {1,2,3,4}; //&b是一个数组指针 b是数组类型但传值时为数组的首地址类型为整型指针 (b+0)经过运算后是一个整型指针
int (*p)[4]; //p就是数组指针,指向长度为4的一维整型数组
//int *q[4]; //指针数组
int i,j;
p=a; //p+1 指向第二排,*(p+1)为数组类型,*(p+1)+1为整型指针 *(*(p+1)+1)解引用取到int类型的值
//for(i=0;i<3;i++){
// for(j=0;j<4;j++){
// printf("%3d",*(*(p+i)+j)); //相当于p[i][j ]
// }
//}
//任何类型取地址,变成该类型指针,然后加1,都会指向该变量的末尾
system("pause");
}
- 二级指针的传递
#include <stdio.h>
#include <stdlib.h>
void change(int **pi,int *pj) {
*pi = pj;
}
//当子函数中需要修改主函数某一个一级指针变量的值,需要用到二级指针
int main() {
int i=10;
int j=5;
int *pi=&i;//在子函数中改变当前函数的值需要把该变量的地址值传递给子函数
int *pj=&j;
//int **p2 = π //二级指针存储的是一级指针的变量的地址值 //&&a为&a已经为常量这样是错误的写法
printf("i=%d j=%d *pi=%d *pj=%d\n",i,j,*pi,*pj);
change(&pi,pj);
printf("i=%d j=%d *pi=%d *pj=%d\n",i,j,*pi,*pj);
system("pause");
}
- 二级指针的偏移
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 5
//二级指针偏移多服务于指针数组
int main() {
char b[N][10] = {"lilei","hanmeimei","xiongda","xionger","lisi"};
//数组名存储的是该数组的首地址,类型是指向数组元素的指针类型
char *p[N]; //p为指针数组但传值时为此数组的首地址,类型为二级指针char** p+0的类型为二级指针
//类比int a[N] a为整型数组但传值时为数组的首地址,类型为整型指针int * a+0的类型为整型指针
//如果数据量超过栈的大小那么就需要在堆上申请内存
//char **p2 = (char **)malloc(sizeof(char *)*5);
char **p2 = p;
int a[5] = {3,7,9,2,4};
int i,j;
char *tmp;
for(i=0;i<N;i++){
//p[i]=b[i]; //b[i]等价*(b+i)值为字符指针
p2[i]=b[i];
}
for(i=0;i<N;i++){
//puts(p[i]);
puts(p2[i]);
}
//冒泡排序法对字符指针进行排序 指针值排序 索引值排序
for(i=0;i<4;i++){
for(j=0;j<4-i;j++){
if(strcmp(p2[j],p2[j+1])>0){
tmp = p2[j];
p2[j]=p2[j+1];
p2[j+1]=tmp;
}
}
}
printf("-------------------------------\n");
for(i=0;i<N;i++){
//puts(p[i]);
puts(p2[i]);
}
system("pause");
}
- 函数指针
#include <stdio.h>
#include <stdlib.h>
void b() {
printf("I am function b");
}
void a(void (*p)()) {
p();
}
int main() {
void (*p)();//函数指针,用于存储函数的入口地址
//p=b;
//p();
a(b); //传递一种行为给函数a
system("pause");
}
函数声明、递归、变量作用域
- 函数的声明与定义
//头文件func.h
#include <stdio.h>
#include <stdlib.h>
//函数的声明
int printstart(int);
void printmessage();
//func.c函数定义文件
#include "func.h"
//函数的定义
int printstart(int i) { //有参函数
printf("*******\n");
return i+3;
}
void printmessage(){ //无参函数
printstart(0);
printf("how do you do");
}
//main.c主程序文件
#include "func.h"
//增量编译 只编译修改的文件
//函数声明和调用最好复制函数名,否则容易出现无法解析的外部符号
int main() {
int i = 10;
//链接 func.obj里的函数的入口地址,替换main.obj中的函数符号
int a = printstart(i);//函数的返回值要立刻接收,函数结束栈空间就被释放
printmessage();
system("pause");
}
- 全局变量
#include <stdio.h>
#include <stdlib.h>
int i=10; //全局变量存储在数据段,不会因为某个函数的结束而消失,一直占用内存空间,所以尽量避免使用全局变量
void print(int a) {
printf("print i=%d\n",i);
}
int main() {
int i=7; //就近原则,避免全局变量与局部变量重名
printf("main i=%d\n",i);
i = 5;
print(i);
system("pause");
//esp寄存器 pc指针 代码段执行到哪一行
//寄存器 栈顶指针
}
- setjmp与longjmp
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
jmp_buf envbuf;
void b() {
printf("I am b func \n");
longjmp(envbuf,5); //直接回到envbuf存储的上下文位置,栈顶指针直接回到保存位置并将i赋值为5,赋值为0会死循环,但内部做了优化不会使值为0
}
void a() {
printf("I am a func \n");
b();
printf("finish b\n");
}
int main() {
int i;
i=setjmp(envbuf); //将当前进程的上下文保存在envbuf
if(0==i){ //将常数放在==的前面,防止误将==写成=
a();
}
system("pause");
}
- 递归实现阶乘
#include <stdio.h>
#include <stdlib.h>
//递归 自身调用自身
int f(int n){
if(n==1){
return 1;
}
return n*f(n-1);
}
//递归实现走台阶 递归不断地压栈弹栈 执行效率不如非递归
int step(int n) {
if(n==1){
return 1;
}
if(n==2){
return 2;
}
return step(n-1)+step(n-2);
}
int main() {
int n;
int i,first,second,total;
//while(scanf("%d",&n)!=EOF){
// printf("%d!=%d",n,f(n));
//}
//一个人上台阶,台阶总计有n个台阶,1次只能走一个台阶,2个台阶,走到n个台阶有多少种走法?
//while(scanf("%d",&n)!=EOF){
// printf("%d层台阶有%d种走法",n,step(n));
//}
while(scanf("%d",&n)!=EOF){
//非递归实现走台阶 已知公式f(n) = f(n-1) +f(n-2) 类似费波纳序列
//任何递归都可以拆成非递归实现
first = 0;
second = 1;
for(i=0;i<=n;i++){
total = first +second;
first = second;
second = total;
}
}
system("pause");
//汉诺塔问题
//先将n-1个盘子借助从a借助c移动到b
//将最后一个盘子从a移动到c
//最后将n-1个盘子从b借助a移动到c
}
- 局部变量及全局变量的作用
//func.h头文件
#include <stdio.h>
#include <stdlib.h>
//变量定义和借用(extern)是不会往头文件中写的
void print();
//func.c
#include "func.h"
extern int k;
//函数默认是extern可以在其他文件中使用 static修饰函数,则该函数只在本文件有效
/*static*/ void print(){
static int i=0; //不会随函数的结束而消失,只会初始化一次
i++; //常量,全局变量,静态变量都是放在数据段
printf("k=%d\n",k);
}
//main.c
#include "func.h"
extern int k; //extern 可以扩展外部变量的作用域
//void print(){
//static修饰局部变量
// static int i=0; //不会随函数的结束而消失,只会初始化一次
// i++; //常量,全局变量,静态变量都是放在数据段
//不同函数内部的static变量名可以相同,因为编译过后不存在变量名都是地址的形式
// printf("k=%d\n",k);
//}
//static 修饰全局变量表示只在本文件内有效不可以再用extern在其他文件扩展作用域 全局变量本身在多个文件内有效
/*static*/ int k=10; //全局变量即外部变量从定义位置到文件末尾有效
int main() {
int i;
{
int j; //内部变量,在离自己最近的大括号有效
}
//j=10;
print();
print();
system("pause");
//register int a 将局部变量的值放在寄存器内用于频繁运算,寄存器不必反复与内存进行读写操作
}
结构体、共用体和枚举
- 结构体定义
#include <stdio.h>
#include <stdlib.h>
struct student {
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
}; //结构体类型声明,注意最后一定要加分号
//cpu只能以4的整数倍的位置在内存中取数据
//结构体是有复杂的对齐规则的 为了存取数据方便可能会舍弃所定义的两个变量之间的字节,以及末尾剩余的字节 &s+1 会舍弃后面不足4的字节
//小字节的最好放在一起并置于最后会节省空间
int main() {
struct student s={1001,"lele",'M',20,89,"北京"};
struct student sarr[3]; //结构体数组
int i;
//结构体数组的读写操作
for(i=0;i<3;i++){
//%d和%s会忽略空格 而%c不会,所以加上一个空格格式匹配
scanf("%d%s %c%d%5.2f%s",&sarr[i].num,sarr[i].name,&sarr[i].sex,&sarr[i].age,&sarr[i].score,sarr[i].addr);
}
printf("------------------------------------------");
for(i=0;i<3;i++){
printf("%d %s %c %d %5.2f %s",sarr[i].num,sarr[i].name,sarr[i].sex,sarr[i].age,sarr[i].score,sarr[i].addr);
}
printf("%d %s %c %d %5.2f %s",s.num,s.name,s.sex,s.age,s.score,s.addr);
}
- 结构体指针
#include <stdio.h>
#include <stdlib.h>
struct student {
int num;
char name[20];
char sex;
};
int main() {
struct student s = {1001,"lilei",'M'};
struct student sarr[3] = {1002,"zhangsan",'M',1005,"lisi",'W',1007,"daming",'M'};
struct student *p;
int num;
p=&s;
//*p.num .的优先级高于* 所以要写成(*p).num
printf("%d %s %c",(*p).num,(*p).name,(*p).sex);
printf("%d %s %c",p->num,p->name,p->sex);
p=sarr;
//结构体与后++
num = p->num++; //num=p->num p->num=p->num+1 []().->的优先级要高于++
printf("num=%d p->num=%d",num,p->num); //1002 1003
num = p++->num; //num=p->num p=p+1
printf("num=%d p->num=%d",num,p->num); //1003 1005
system("pause");
}
- typedef的使用
#include <stdio.h>
#include <stdlib.h>
//#define在预处理过程
//typedef在编译过程
typedef struct /*student可省略*/ {
int num;
char name[20];
char sex;
}stu,*pstu; //stu是一个类型别名 pstu等价于struct student*
int main() {
stu s={1001,"lilei",'M'};
pstu p;
p=&s;
printf("%d %s %c\n",p->num,p->name,p->sex);
system("pause");
}
- 共用体和枚举
#include <stdio.h>
#include <stdlib.h>
//共用体
union data {
int a;
char c;
float f;
};
//共用体的大小是其中最大类型变量的大小,一个时间内只能存取一个成员
//每个成员的起始地址是相同的
//枚举
enum weekday {sun,mon,tue,wed,thu,fri,sat}; //sun-sat都是枚举常量
int main() {
union data d; //共用体不能直接初始化,因为不清楚此值是什么类型
enum weekday workday; //枚举变量可赋值
workday=mon;
printf("sun=%d",sun); // sun是枚举常量
d.a=4;
d.c='A';
d.f=98.5;
system("pause");
}
文件操作、realloc、qsort
- 文件打开与关闭
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[]) { //main函数的参数,argc保存的是参数的总个数,argv保存的是参数的字符指针,默认第一个是文件的绝对路径,剩下的参数由用户输入
FILE *fp;
int i;
char c;
int ret;
//printf("%d\n",argc);
//for(i=0;i<argc;i++){
// puts(argv[i]);
//}
fp=fopen(argv[1],"r+"); //打开用户传入的参数
if(NULL==fp){
perror("fopen"); //定位函数出现错误的原因,只能定位刚刚执行的函数,参数是函数名
goto error;
}
//while((c=fgetc(fp))!=EOF){ //调用fgetc后fp所指向的结构体内的fptr自动向后偏移一位指向下一个字符,若文件被读取完,会返回-1
// printf("%c",c);
//}
c='H'; //全缓冲:磁盘文件的读写 行缓冲:标准输入stdin 输出stdout 不带缓冲:stderr出错信息
ret=fputc(c,fp); //此处打断点会发现文件中并未添加内容,是因为并未直接写入到磁盘,而是先写入到缓冲区,缓冲区刷新才会写入到磁盘
printf("\n"); //fclose(fp),进程结束时系统会关闭fp 缓冲区满的时候 fflush(fp)主动刷新
fclose(fp);
error:
system("pause");
}
- fread和fwrite
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define N 20
int main(int argc, char *argv[]) {
FILE *fp;
char buf[N]={0}; //一定要进行初始化为0,因为如果读取字符串到buf,是不会将末位赋/0的,输出的时候就有问题
int ret;
fp=fopen(argv[1],"r+");
if(NULL==fp){
perror("fopne");
goto error;
}
//ret=fread(buf,sizeof(char),sizeof(buf),fp); //这里即使指定N或者sizeof(buf),fread最多也只会读取到文件末尾就结束 为避免读取内容到buf导致buf没有结束符,可将sizeof(buf)改为sizeof(buf)-1,保留一位结束符
//printf("ret=%d,buf=%s",ret,buf);
strcpy(buf,"world");
ret=fwrite(buf,sizeof(char),strlen(buf),fp); //这里要用strlen,用sizeof(buf)会将末尾多余的0写入到文件中
fclose(fp);
//一个fp只有一个缓冲区,既可以读又可以写
//stdin FILE * stdout FILE * 是两个缓冲区,一个只读,用scanf读 一个只写,用printf写
//全缓冲 fp fclose(fp) 缓冲区满,写到磁盘 主动刷新 行缓冲 stdin stdout
error:
system("pause");
}
- 文件模式与二进制模式
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
FILE *fp;
int i=8;
int ret;
char buf[128]="hello\nworld";
fp=fopen(argv[1],"w+"); //文本模式 r打开读取 w创建写入 a附加 读写r+ w+ a+ 二进制模式 rb rb+ wb wb+ ab ab+
if(argc!=2){ //创建会将原来的数据清空
printf("error argc\n");
goto error;
}
if(NULL==fp){
perror("fopen");
goto error;
}
printf("strlen(buf)=%d\n",strlen(buf));
//ret=fwrite(&i,sizeof(int),1,fp); //返回值是已写多少个size字节的数据
//以文本模式写入的就以文本模式读取 同样以二进制写入的就以二进制读取
ret=fwrite(buf,sizeof(char),strlen(buf),fp); //如果以文本模式r+打开文件写入\n存在磁盘上的是\r\n,读取时遇到\r\n会自动变成\n 二进制模式rb+写入\n存在磁盘上的就是\n
ret=fseek(fp,0,SEEK_SET); //传入文件类型指针 偏移量 起始位置 成功返回0,不成功返回非0 SEEK_SET 0 SEEK_CUR 1 SEEK_END 2
memset(buf,0,sizeof(buf)); //偏移是按照磁盘上存储的数据进行偏移 文本模式\n变成\r\n要多偏移一个
ret=fread(buf,sizeof(char),sizeof(buf),fp);
fclose(fp);
printf("%s\n",buf);
error:
system("pause");
}
- fgets与fputs
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv) {
FILE *fp;
int ret;
char c;
char buf[1024]={0};
fp=fopen(argv[1],"r+"); //文本模式 r打开读取 w创建写入 a附加 读写r+ w+ a+ 二进制模式 rb rb+ wb wb+ ab ab+
if(argc!=2){ //创建会将原来的数据清空
printf("error argc\n");
goto error;
}
if(NULL==fp){
perror("fopen");
goto error;
}
while(fgets(buf,sizeof(buf),fp)!=NULL){ //成功返回字符串,否则返回NULL
//puts(buf); fgets读取时就保留了第一行字符串以及换行符,再用puts输出会多一个换行符
printf("%s",buf); //fgets最多读取sizeof(buf)个字符,它会自动为buf末尾添加\0,他是安全的
buf[0]=buf[0]-32; //将首字符修改为大写
if(buf[strlen(buf)-1]=='\n'){
fseek(fp,-1*strlen(buf)-1,SEEK_CUR); //有\n要多偏移一位
}else{
fseek(fp,-1*strlen(buf),SEEK_CUR); //最后一行没有\n的情况
}
fputs(buf,fp);
fseek(fp,0,SEEK_CUR); //读写之间fseek刷新光标位置
}
fgets(buf,sizeof(buf),stdin);
printf("%s",buf);
ret=fputs("world",stdout); //末尾是没有换行符的
fclose(fp);
error:
system("pause");
}
- fscanf和fprint
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int num;
char name[20];
float score;
}stu;
int main(int argc, char **argv) {
FILE *fp;
int ret;
char c;
stu s;
fp=fopen(argv[1],"r+"); //文本模式 r打开读取 w创建写入 a附加 读写r+ w+ a+ 二进制模式 rb rb+ wb wb+ ab ab+
if(argc!=2){ //创建会将原来的数据清空
printf("error argc\n");
goto error;
}
if(NULL==fp){
perror("fopen");
goto error;
}
//while(fscanf(fp,"%d%s%f",&s.num,s.name,&s.score)!=EOF){ //从文件中循环读取 返回值为成功匹配赋上值的个数
// printf("%d %s %5.2f\n",s.num,s.name,s.score);
//}
// scanf("%d%s%f",&s.num,s.name,s.score); 相当于fscanf(stdin,"%d%s%f",&s.num,s.name,&s.score);
fscanf(fp,"%d%s%f",&s.num,s.name,&s.score); //格式化读取 格式化输入
// printf("%d %s %5.2f",s.num,s.name,s.score); 相当于fprintf(stdout,"%d %s %5.2f",s.num,s.name,s.score);
fseek(fp,0,SEEK_CUR);
fprintf(fp,"new=%d %s %5.2f",s.num,s.name,s.score); //格式化输出
fclose(fp);
error:
system("pause");
}
- realloc
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr=(int*)malloc(20);
int *p;
arr[0]=1;
arr[1]=2;
arr[2]=3;
arr[3]=4;
arr[4]=5;
p=(int *)realloc(arr,40); //将动态数组进行扩大,指针的起始地址不变
p[5]=6; //这时候就不会出现访问越界
printf("arr[5]=%d",arr[5]);
system("pause");
}
- qsort的使用
#include <stdio.h>
#include <stdlib.h>
#define N 3
typedef struct {
int num;
char name[20];
float chinese;
float math;
float english;
}stu,*pstu;
//结构体按照学号升序排序
int compare_num(const void *p1,const void *p2){
pstu pleft=(pstu)p1;
pstu pright=(pstu)p2;
return pleft->num-pright->num;
}
//结构体按照语文成绩升序排序
int compare_chinese(const void *p1,const void *p2){
pstu pleft=(pstu)p1;
pstu pright=(pstu)p2;
//浮点型数据不能直接相减,需要判断
if(pleft->chinese>pright->chinese){
return 1;
}else if(pleft->chinese<pright->chinese){
return -1;
}else{
return 0;
}
}
//根据学号进行索引值排序
int compare_parr(const void *p1,const void *p2){ //参数是任意两个指向所要排序元素的指针,即是结构体的二级指针
pstu *pleft=(pstu *)p1;
pstu *pright=(pstu *)p2;
return (*pleft)->num-(*pright)->num;
}
int main() {
stu arr[N];
pstu parr[N]; //指针数组,用来索引值排序 辅助实现链表排序
int i;
for(i=0;i<N;i++){
scanf("%d%s%f%f%f",&arr[i].num,arr[i].name,&arr[i].chinese,&arr[i].math,&arr[i].english);
parr[i]=arr+i; //将结构体数组中每个结构的指针存储起来
}
// qsort(arr,N,sizeof(stu),compare_num); //利用qsort将结构体数组中的结构体进行排序
for(i=0;i<N;i++){
printf("%d %s %5.2f %5.2f %5.2f",arr[i].num,arr[i].name,arr[i].chinese,arr[i].math,arr[i].english);
}
qsort(parr,N,sizeof(pstu),compare_parr); //利用qsort将结构体指针数组中的指针进行排序 索引排序
for(i=0;i<N;i++){
printf("%d %s %5.2f %5.2f %5.2f",parr[i]->num,parr[i]->name,parr[i]->chinese,parr[i]->math,parr[i]->english);
}
system("pause");
}
C++
C++基本程序
- 第一个C++程序
//头文件c++标准形式
#include<iostream> // c语言中 include<stdio.h>
using namespace std; // 命名空间 此命名空间包含cout cin 等对象
//命名空间 区别同名的变量和函数
//命名空间的声明
namespace std1{
void sort(){
cout << "sort1" << endl;
}
}
namespace std2{
void sort(){
cout << "sort2" << endl;
}
}
//条件编译
#if 0
#endif
//int main(int argc,char *argv[]){ return 0} 带命令行参数的主函数写法
int main(void){
//输出 --- 对代码的简单描述
cout << "hello world" << endl; //endl 换行,并且清空,刷新缓冲区 \n仅是换行
//cout 相较于c中printf 1.可连续输出 2.自动识别类型
cout << "123" << " " << 456 << endl; //cout是个对象,不是关键字,也不是函数
//c++变量的声明不用写在开头
int a = 10;
char b = 'b';
float c = 1.234f;
cout << a << " " << b << " " << c << endl;
//cin 相较于scanf 1.可连续输入 2.自动识别类型类型
cin >> a >> b >> c;
cout << a << " " << b << " " << c << endl;
//命名空间的使用 using namespace 空间名(整个空间对外) using 空间名::变量/函数名 (仅指定变量或函数对外)
using namespace std1;
sort();
//不打开命名空间可以采用 命名空间名::变量名/函数名 :::作用域运算
std2::sort();
system("pause"); //方便观察结果
return 0;
}
- for循环
#include <iostream>
using namespace std;
int main(){
// int i=0;
//c++可在for循环的()内声明变量,在vs中其作用域仅在for循环内,而在vc中其作用域是在main函数内
for(int i=0;i<5;i++){
cout<<i<<endl;
}
for(int i=0;i<5;i++){
cout<<i<<endl;
}
system("pause");
return 0;
}
- struct结构体
#include<iostream>
using namespace std;
struct Node{
int i;
//c++结构体中可定义函数成员 c语言可通过函数指针达到结构体调用函数的效果 void (*p)();
//c++结构体是一个特殊的类
void print(){
printf("hello world");
}
};
int main(void){
Node n; //c++声明结构体变量前面不用加struct 结构体定义的时候就不必使用typedef重命名
n.print();
system("pause");
return 0;
}
- new和delete
#include <iostream>
using namespace std;
int main(void){
//new 空间的申请
int *p =(int *)malloc(sizeof(int));
int *p1= new int; //new type 类型匹配上 new type(10)初始化操作
*p1 = 10; //写
cout << *p1; //读
//delete 空间的释放
free(p);
delete p1; //delete 空间指针
//数组空间的申请
int *s = (int *)malloc(sizeof(int)*5);
int *s1 = new int[5];
//可使用memset对连续的空间进行统一的赋值
memset(s1,0,5*4);
s1[0]=10;
s1[1]=20;
cout <<s1[0] << " " << s1[2] << endl;
//数组空间的释放
free(s);
delete[] s1; //数组的释放要加上[]
//delete NULL是安全的 free(NULL)是崩溃的
//new和delete可以触发对象的构造和析构,malloc和free则不可以
//*星号的三种作用 1.在声明变量的时候 * 指针变量 2.地址操作符 用来读写操作 3. 乘法运算符
//对内存的操作 读 写 取地址&
system("pause");
return 0;
}
- 参数默认值
#include <iostream>
using namespace std;
//参数缺省值/默认值
void fun(int a=10,char c='c'){ //全部指定
cout<<a<<" "<<c<<endl;
}
void fun1(int a,char c='c',float f=1.23){ //部分指定,从右向左连续指定
cout<<a<<" "<<c<<" "<<f<<endl;
}
int main(){
//函数调用,有默认值的参数,可以不用传递实参
fun();
//没指定默认值的,一定要传递实参
fun1(123);
//有默认值,还传递实参,实参会覆盖掉默认值
fun(123);
//如果函数定义写在调用后面,函数声明写在前面,在指定默认值的时候,仅需在函数声明处指定默认值。
system("pause");
return 0;
}
- 函数重载
#include <iostream>
using namespace std;
//函数的声明会变相的扩展函数的作用域
//函数的重载
//同一作用域函数的名字相同,参数类型、个数或者顺序不同,返回值不作为函数重载的条件
void fun(int a){
cout<<"1:"<<a<<endl;
}
//默认参数和函数重载使用可能会造成调用不明确 void fun(int a,float f=1.23){}
void fun(int a,float f){
cout<<"2:"<<a<<" "<<f<<endl;
}
void fun(char c){
cout<<"3:"<<c<<endl;
}
int main(){
//根据参数的类型和个数,系统自动选择调用,使得函数的调用更加灵活
fun(123);
fun(123,1.23f); //注意float类型的参数传递
fun('a');
system("pause");
return 0;
}
- 引用
#include <iostream>
using namespace std;
int main(void){
//引用类似于变量起别名
int a=10;
//声明引用变量 声明的时候必须初始化
//一个引用类型声明初始化后就不能更改
int &c=a; //声明变量a的一个引用c,c是变量a的一个别名
//变量 a c所关联的地址相同这也是引用类型与指针的区别
cout << &a << " " << &c << endl;
int b=12;
c=b; //这表示将b的值赋值给c,而不表示c是b的一个引用
//常量的引用
const int &d=10;
//数组的引用
int arr[10];
int (&p)[10]=arr; //()优先级高表示p是一个引用类变量型,如果没有(),p会先和[]结合表示p为一个数组类型
int arr1[10][10];
int (&p1)[10][10]=arr1;
//指针的引用
int *s=&a;
int *&s1=s; //(&s1)表示s1是一个引用类型变量,int *表示所引用的数据类型是整型指针
system("pause");
return 0;
}
- 引用作参数
#include <iostream>
using namespace std;
//引用作参数,形参与实参关联的是同一块空间
void fun(int &a){
a=13;
cout<<"fun:"<<a<<endl;
}
void fun1(int a){ //不是引用类型作参数,会在栈区为形参在开辟一块空间用于值传递
a=15;
cout<<"fun1:"<<a<<endl;
}
//c语言通过函数改变函数外的值,是通过传递指针实现的
void fun2(int* a){
*a=17;
cout<<"fun2:"<<a<<endl;
}
//通过引用类型完成函数外数据的交换
void ExChange(int &a,int &b){
int temp=a;
a=b;
b=temp;
}
//c语言通过指针交换数据
void ExChange1(int *a,int *b){
int temp=*a;
*a=*b;
*b=temp;
}
int main(void){
int a=10;
int b=20;
cout<<a<<" "<<b<<endl;
ExChange(a,b);
cout<<a<<" "<<b<<endl;
fun(b);
fun1(b);
fun2(&b);
system("pause");
return 0;
}
- 引用作返回值
#include <iostream>
using namespace std;
//返回值为引用类型
int& fun(){
int a=10;
return a;
}
int main(void){
int &b=fun();
//不要引用局部变量,函数调用结束后局部变量空间被释放,引用的是一个非法空间,结果是未知的
//此时的b是一个非法空间的引用
cout<<b<<endl;
//引用与指针的区别: 1.引用声明就要初始化 2.引用初始化后就不能引用其他空间了
//3.引用不占存储空间 4.引用是安全的,指针是可以偏移的 //指针更灵活,直接操作地址
//&的三种作用 1.声明变量&表示引用类型 2.变量前面&表示取地址 3.数&数表示位与运算
system("pause");
return 0;
}
面向对象思想——类
- class
#include <iostream>
using namespace std;
class CPeople{ //类似定义一个struct C++中struct是一种特殊的类
public: //访问修饰符
//数据成员 属性
int a;
//函数成员 行为
void fun(){
cout<<"fun"<<" "<<a<<endl;
}
};
int main(){
//栈区变量
CPeople op;
op.a=12;
op.fun();
//堆区变量
CPeople *op1=new CPeople;
op1->a=15;
op1->fun();
delete op1;
system("pause");
return 0;
}
- this
#include <iostream>
using namespace std;
class CStu{
public:
int age;
//局部变量与外部变量重名,那么就近原则使用局部变量 所以使用this来区别局部变量和成员变量
//this为指向当前对象的指针CStu* this仅当对象创建的时候才有的 this不是成员,是类(非静态)成员函数的隐含参数,只能在成员函数中使用
CStu(int age){
this->age=age;
}
void Show(){
cout<<age<<endl;
}
CStu* GetAddr(){
return this;
}
};
int main(){
CStu stu(12);
stu.Show();
system("pause");
return 0;
}
- const和static
#include <iostream>
using namespace std;
//const static类型修饰符
class CStu{
public:
int age;
static int a; //类外初始化 不能通过初始化列表初始化
static const int b=100; //只有静态常量整型数据成员才能类内直接初始化,也可以类外初始化
CStu(){ //this指针是CStu* 类型的
age=10;
a++;
}
//常函数 可以使用数据成员但不能修改数据成员 构造函数和析构函数不允许是常函数
//常对象只能调用常函数,不能调用普通函数
void Show() const{ //this指针是const CStu*类型的
cout<<"I am Show()"<<endl;
}
static void fun(){ //静态成员函数只能使用静态成员并且没有this指针
cout<<"I am static"<<endl;
}
};
//静态成员可理解为类的一种属性,与对象没有什么直接关系,在代码预处理的时候便分配了空间,普通成员在对象创建的时候才分配空间
//一个类的对象都共用一个静态成员
//类外初始化
int CStu::a=13;
int main(){
CStu stu;
stu.Show();
//类名作用域调用static数据成员和函数成员
cout<<CStu::a<<endl;
CStu::fun();
//对象调用static数据成员和函数成员
cout<<stu.a<<endl;
stu.fun();
//数组声明5个CStu类型的对象,便调用了5次构造函数,静态成员a自增了5次
CStu arr[5];
system("pause");
return 0;
}
- 访问修饰符
#include <iostream>
using namespace std;
class CPeople{
//访问修饰符作用范围:书写位置开始到下个修饰符,或者到类结尾的花括号
public: //类成员访问修饰符 C++struct内默认是public 类外可见:对其他类可见 对自定义函数可见 对main函数可见
//private // 私有的 类内默认是private 类内可见
int a;
protected: //类外不可见 类内以及类的子类可见
void fun(){
cout<<"fun"<<" "<<a<<endl;
}
// private:纯私有 类内使用 protected:受保护 类内和子类可使用 public:公有的 都可以使用
};
class CP : public CPeople{ // 继承CPeople
void fun1(){
fun(); // 使用父类函数
}
};
int main(){
CPeople op;
op.a=12;
// op.fun();
system("pause");
return 0;
}
- friend友元
#include <iostream>
using namespace std;
class CStu{
private:
int age;
void fun(){
age=12;
cout<<age<<endl;
}
public: //接口函数思想 通过公有函数访问或修改私有成员
int Get(){
return age;
}
friend void fun1(); //声明fun1为类CStu的友元函数 类内成员对fun1友元函数完全可见相当于public
friend class CTeach; //声明CTeach为CStu的友元类 类内成员对CTeach友元类完全可见
};
class CTeach{ //类访问protected成员方法:继承 友元 访问private成员方法:友元
public:
CStu stu;
void fun2(){
stu.fun();
}
};
void fun1(){
CStu stu;
stu.fun();
}
int main() {
fun1();
CTeach teach;
teach.fun2();
system("pause");
return 0;
}
- 构造函数
#include <iostream>
using namespace std;
class CStu{
public:
int age;
float f;
//构造函数,给数据成员赋值 在对象创建的过程中自动调用
//没有返回值
CStu(){
age=12;
f=12.12f;
}
//一个类构造函数可以有多个,重载关系
CStu(int a,float b){
age=a;
f=b;
}
//若未写构造函数,则会使用默认构造函数,若自定义了构造函数,默认构造函数则被覆盖
//CStu(){
//
//}
//成员函数可以类内声明,类外定义 此方式用于多文件使用
//内类声明,函数原型
void fun(int);
};
//类外定义,函数名之前要加上类名作用域
void CStu::fun(int a){
cout<<a<<endl;
}
class CTeach{
public:
int age;
float f;
//带参数的构造函数
CTeach(int a,float b=11.11f){ //可指定默认值
age=a;
f=b;
}
};
int main(){
//栈区对象
CStu stu;
cout<<stu.age<<endl;
//堆区对象
CStu *stu1=new CStu;
cout<<stu1->age<<endl;
delete stu1;
//带参数的初始化
CTeach teach(11,13.12f);
CTeach *teach1=new CTeach(13,11.22f);
cout<<teach.age<<endl;
cout<<teach1->age<<endl;
delete teach1;
system("pause");
return 0;
}
- 初始化与赋值
#include <iostream>
using namespace std;
int main() {
int a=12; //初始化
int b;
b=13; //赋值
int arr[10]={1,2,3}; //初始化
int arr1[10]; //声明
//数组声明后不能用{1,2,3}这种方式赋值
arr1[0]=1;
arr1[1]=2;
arr1[2]=3;
//结构体与数组方式类似
struct stu{
int a;
};
stu s={12};
stu s1;
s1.a=12;
//引用类型与const修饰的变量只能初始化,是不能进行赋值的
int &s=a;
const int c=10;
system("pause");
return 0;
}
- 初始化列表
#include <iostream>
using namespace std;
struct STU{
int i;
float f;
};
class CStu{
public:
int age;
float f;
//初始化列表 给数据成员初始化 初始化顺序只与数据成员声明顺序有关,与初始化列表顺序无关
//可通过数值、构造函数参数、成员之间来对数据成员进行初始化
CStu(int a):age(a),f(12.12f){ //初始化列表先于函数体内的构造函数赋值
//构造函数 给数据成员赋值
age=13;
}
};
class CTeach{
public:
int a;
int &b;
const int e;
//引用类型和const修饰变量必须要进行初始化
CTeach(int &c):a(c),b(c),e(123){
}
//若有多个构造函数,执行哪个构造函数就执行哪个初始化列表
};
class CPeople{
public:
int a[4];
STU st;
//数组初始化,使初始值为0,但不是所有编译器都支持
//结构体初始化,c++中结构体是可以直接相互赋值的
CPeople(STU sd):a(),st(sd){
//个人写法
for(int i=0;i<4;i++){
a[i]=0;
}
//系统提供函数,给一段连续空间设置指定值
memset(a,0,4*sizeof(int));
}
void Show(){
for(int i=0;i<4;i++){
cout<<a[i]<<endl;
}
}
};
int main(){
CStu stu(11);
cout<<stu.age<<endl;
int c=10;
CTeach teach(c);
cout<<teach.a<<" "<<teach.b<<endl;
STU st={10,10.10f};
CPeople cp(st);
system("pause");
return 0;
}
- 析构函数
#include <iostream>
using namespace std;
class CStu{
public:
int age;
int *p;
CStu(){
//构造函数给成员变量赋值
p=new int(10);
cout<<"构造函数"<<endl;
}
//析构函数 析构函数只有一个,不能重载,没有参数 在空间释放的时候自动调用
//
~CStu(){
//析构函数做清理工作
delete p;
cout<<"析构函数"<<endl;
}
};
int main(){
{ //局部对象 作用域是相邻的大括号内 出作用域或函数结束释放空间,调用析构函数
CStu stu;
}
CStu *stu=new CStu;
//指针对象delete的时候才调用析构函数
delete stu;
//临时对象 作用域是当前语句 语句结束就释放空间调用析构函数
CStu();
//malloc和new new会触发对象的构造函数,malloc则不会
//free和delete delete会触发对象的析构函数,free则不会
system("pause");
return 0;
}
- 拷贝构造
#include <iostream>
using namespace std;
class CStu{
public:
int age;
char c[4];
CStu(){
age=10;
strcpy(c,"abc");
}
//拷贝构造 本质是构造函数,参数是本类的引用
//默认的拷贝构造,逐个复制非静态成员的值(浅拷贝) 同一个类的多个对象,内存排布是一样的
CStu(const CStu &a){ //手写拷贝构造实现类似默认拷贝构造功能
this->age=a.age;
strcpy(c,a.c);
}
};
void fun(CStu a){} //a通过传入的实参stu来进行拷贝构造,完成初始化
CStu fun1(){
CStu a;
return a; //返回值返回的是一个临时对象 通过a拷贝构造出的一个临时对象 stu=CStu(a);
}
int main(){
//新建一个对象并将其初始化为同类的现有对象 调用拷贝构造来完成
CStu stu;
CStu stu1(stu);
CStu stu2=stu;
CStu stu3=CStu(stu);
CStu *stu4=new CStu(stu);
//这种情况不会调用拷贝构造
CStu stu5; //此时对象已经创建完毕,不会再调用构造函数
stu5=stu; //也就不会调用拷贝构造 是通过运算符重载实现的
//当程序生成对象副本的时候 调用拷贝构造
fun(stu); //作参数情况
fun1(); //作返回值情况
system("pause");
return 0;
}
- 深拷贝
#include <iostream>
using namespace std;
class CStu{
public:
int *p;
CStu(){
p=new int[2];
p[0]=10;
p[1]=20;
}
CStu(const CStu &a){
//this->p=a.p; 默认拷贝构造实现的功能(浅拷贝)
//深拷贝
//额外申请指针所指的空间
this->p=new int[2];
//将a数据成员指针所指空间的值复制到自己指针所指的空间
p[0]=a.p[0];
p[1]=a.p[1];
//memcpy(p,a.p,sizeof(int*2)) 内存拷贝
}
~CStu(){
delete[] p;
}
};
int main(){
{
CStu stu;
cout<<stu.p[0]<<" "<<stu.p[1]<<endl;
CStu stu1=stu; //浅拷贝单纯将指针的值进行复制,两个指针指向同一片内存空间
cout<<stu1.p[0]<<" "<<stu1.p[1]<<endl;
}//出作用域时调用了两次析构函数 同一片内存空间释放了两次,程序崩溃
//解决拷贝构造所引发的,指针成员二次释放的问题:1.深拷贝 2.传地址 3.传引用 2、3通过避免拷贝构造不产生临时变量,直接操作传入或返回的参数解决的
system("pause");
return 0;
}
- 继承
#include <iostream>
using namespace std;
class CPeople { //基类 父类
public:
void Study(){
cout<<"Study"<<endl;
}
protected:
void fun(){
cout<<"protected fun"<<endl;
}
private:
void fun1(){
cout<<"private fun"<<endl;
}
};
//继承的写法
class CChild : public CPeople{ //派生类 子类
public:
void GoToSchool(){
cout<<"GoToSchool"<<endl;
}
};
class CMan : public CPeople{
public:
void GoToWork(){
cout<<"GoToWork"<<endl;
}
};
//private子类不能访问 protected子类可以访问 public子类以及类外都可以访问
//继承的限定修饰符 public 父类修饰符保持不变 (访问权限不变)
// protected 父类的public降级成protected private和protected不变(高级降为同级,同级及以下不变,降低访问权限)
// private 全部降级为private 降低访问权限
class COldMan : public CPeople{
public:
void GoToPark(){
cout<<" GoToPark"<<endl;
}
};
int main(){
CChild child;
child.Study();
CMan man;
man.Study();
system("pause");
return 0;
}
- 构造函数的继承
#include<iostream>
using namespace std;
class CPeople{
public:
CPeople(){
cout<<"CPeople"<<endl;
}
};
class Cxiaoming : public CPeople{
public:
Cxiaoming(){
cout<<"Cxiaoming"<<endl;
}
};
class Cxiaohua : public Cxiaoming{
public:
Cxiaohua(){
cout<<"Cxiaohua"<<endl;
}
};
int main(){
//继承构造的的调用顺序 先父类后子类
Cxiaoming xiaoming;
Cxiaohua xiaohua; //实例化小华要先实例化小明,实例化小明要先实例化CPeople
system("pause");
return 0;
}
- 有参构造函数的继承
#include <iostream>
using namespace std;
class CgrandFather{
public:
CgrandFather(int a,int b){
cout<<"CgrandFather"<<endl;
}
//若父类有多个构造函数 那么根据子类初始化列表决定调用哪个构造函数
CgrandFather(int a){
cout<<"CgrandFather"<<endl;
}
~CgrandFather(){
cout<<"~CgrandFather"<<endl;
}
};
class CFather : public CgrandFather{
public:
CFather(int a) : CgrandFather(a,2){
cout<<"CFather"<<endl;
}
~CFather(){
cout<<"~CFather"<<endl;
}
};
class CSon : public CFather{
public:
int age;
//父类有参构造函数 要在子类构造函数初始化列表 给父类构造函数传递参数
//默认是调用父类无参构造函数 CSon() : CFather(){}
CSon() : CFather(1){
cout<<"CSon"<<endl;
}
~CSon(){
cout<<"~CSon"<<endl;
}
};
int main(){
{
//构造函数先调用辈分大的再调用辈分小的
CSon son;
//析构函数先调用辈分小的再调用辈分大的
}
system("pause");
return 0;
}
- 覆盖
#include <iostream>
using namespace std;
class CFather {
private:
int a;
public:
//静态成员只有一份
static int b;
int age;
int sex;
CFather() {
age=12;
sex=1;
}
void fun(){
cout<<"CFather"<<endl;
}
void fun(int i){
cout<<i<<endl;
}
//父类的友元函数是不能被子类继承的
friend void show();
};
class CSon : public CFather{
public:
int age;
CSon(){
age=10;
}
void printage(){
//子类中出现与父类同名数据成员 默认使用的是子类的 通过类名作用域来访问父类数据成员
cout<<age<<endl;
cout<<CFather::age<<endl;
}
void fun(){
cout<<"CSon"<<endl;
}
};
void show(){
CFather father;
cout<<father.a<<endl;
}
int main(){
CSon son;
cout<<son.age<<endl;
//通过类名作用域来访问父类同名数据成员
cout<<son.CFather::age<<endl;
son.fun();
//通过类名作用域来访问父类同名函数成员 父类和子类同名函数没有重载关系,只要函数名相同就覆盖
son.CFather::fun();
//不同名的数据成员或者函数成员 子类对象可以直接访问不用使用类名作用域区分
cout<<son.sex;
system("pause");
return 0;
}
- 内联函数
#include <iostream>
using namespace std;
#define SUM(x) x*x
//常规函数调用 跳转到所调用函数在内存中的代码段,执行结束再跳回主函数
//内联函数调用 直接将所调用的函数的相应代码复制替换到主函数中
//内联函数的声明
//声明和定义前都要加上inline
inline void fun();
class CStu{
public:
//类内定义的函数都是内联函数,隐式定义和显式定义
void fun(){
}
inline void fun1(){
}
void fun2();
};
//类外定义的有inline的是内联函数,没有inline便不是内联函数
inline void CStu::fun2(){}
int main(){
//内联函数比宏的功能更强
cout<<SUM(2+3)<<endl;
fun(2+3);
system("pause");
return 0;
}
//内联函数定义
inline void fun(int i){
cout<<i*i;
}
- 虚函数
#include <iostream>
using namespace std;
//多态与虚函数
//多态 父类的一个指针,可以有多种执行状态 相同的代码实现不同的功能 父类的指针调用子类的函数
//虚函数是实现多态的语法基础
class CFather {
public:
//若父类函数没有virtual关键字 因为是父类指针类型 所以只能调用父类的函数
//若父类函数有virtual关键字 那么父类指针便可以调用子类函数成员
//虚函数子类和父类函数名要相同
//父类指针指向哪个子类就调用哪个子类的函数
virtual void show(){
cout<<"CFather"<<endl;
}
};
class CSon : public CFather{
public:
int age;
void show(){
cout<<"CSon"<<endl;
}
};
int main(){
//父类的指针指向子类的空间
CFather *fa=new CSon;
fa->show();
system("pause");
return 0;
}
- 虚函数特点
#include <iostream>
using namespace std;
class CFather {
public:
virtual void show(){
cout<<"CFather"<<endl;
}
};
class CSon : public CFather{
public:
int age;
//重写 针对于相同函数父类是虚函数的情况 父类是普通函数则是覆盖
//子类重写的函数默认是虚函数 可显式加virtual 也可以不加
//返回值以及函数参数完全相同才构成重写关系 返回值不同是协变 只允许相同函数返回各自类的引用类型
//构造函数 内联函数 不能是虚函数
virtual void show(){
cout<<"CSon"<<endl;
}
};
int main(){
CSon son;
system("pause");
return 0;
}
//虚表是维护一个对象虚函数的列表 此对象父类的虚函数地址被存储在表中 类似一个指针数组
//若这个子类重写了父类的虚函数,那么虚表中虚函数地址会修改成子类重写的函数地址
//多态实现过程
//1.根据父类的指针,找到父类的函数 2.看所调用的函数是不是父类的虚函数
//是则进入虚表按表中地址执行函数(自己/重写) 不是则执行自己的函数
- 取虚表的内容
#include <iostream>
using namespace std;
class CFather {
public:
virtual void fun(){
cout<<"CFather"<<endl;
}
virtual void show(){
cout<<"CFather"<<endl;
}
};
class CSon : public CFather{
public:
int age;
virtual void fun(){
cout<<"CSon"<<endl;
}
virtual void show(){
cout<<"CSon"<<endl;
}
};
int main(){
//对象空间的最开始四字节内容,就是虚表(虚函数列表)的地址,叫虚指针
CFather *fa=new CSon;
//先将CFather*类型强转成int*类型取值为虚表地址,再强转为int*类型便于在虚表中进行指针偏移,最后再取*得到的是虚函数地址
typedef void (*p)();//将void (*)()类型重命名为p
*((int*)*(int*)fa+0); //fun函数地址
*((int*)*(int*)fa+1); //show函数地址
*((int*)*(int*)fa+2); //虚表结尾0x00000000 也占四个字节
((p)*((int*)*(int*)fa+0))();//调用fun函数 先强转为对应函数类型指针然后()调用
system("pause");
return 0;
}
- 联编
#include<iostream>
using namespace std;
class CA{
public:
virtual void fun(){
cout<<"CA"<<endl;
}
};
class CB : public CA{
public:
virtual void fun(){
cout<<"CB"<<endl;
}
};
void fun(){
cout<<"fun"<<endl;
}
int main(){
CA a;
fun(); //静态联编 在编译过程中决定的
CA *ca=new CB;
ca->fun(); //静态联编 编译过程中就已经决定调用哪个函数
int i;
cin>>i;
CA *p;
switch(i){
case 1:
p=new CA;
break;
case 2:
p=new CB;
break;
}
p->fun(); //运行的时候才能决定到底调用谁的fun()函数
system("pause");
return 0;
}
- 纯虚函数
#include <iostream>
using namespace std;
class CFather {
public:
//纯虚函数特点 1.可以没有函数实现 2.类中有纯虚函数是不能实例化对象的 3.有纯虚函数的类必须用子类重写纯虚函数,才能实例化该子类对象
//有纯虚函数的类 叫做抽象类 除数据成员和构造函数之外所有函数都是虚函数 叫做接口类
//纯虚函数写法
virtual void fun()=0;
virtual ~CFather()=0;
};
class CSon : public CFather{
public:
void fun(){
cout<<"CSon"<<endl;
}
};
int main(){
system("pause");
return 0;
}
- 虚析构
#include <iostream>
using namespace std;
class CFather {
public:
~CFather(){
cout<<"CFather"<<endl;
}
};
class CSon : public CFather{
public:
~CSon(){
cout<<"CSon"<<endl;
}
};
class Cxiaoming{
public:
~Cxiaoming(){
cout<<"Cxiaoming"<<endl;
}
};
int main(){
CFather *fa=new CSon;
//在实例化子类的时候,实际上是先实例化父类再实例化子类,申请了父类成员空间和子类成员空间两部分空间
//若不使用虚析构那么释放fa指针,只会调用父类的析构函数而不会调用子类的析构函数
//使用析构函数则在释放父类指针时,子类的析构函数和父类的析构函数都会被调用
// delete fa;
//delete调用析构函数只看指针类型 指针是谁的类型就调用谁的析构函数 指针所指的空间仍被释放掉
//类型错误不会造成对象空间泄露 但有可能造成对象成员申请空间泄露(原本在析构函数中释放的空间)
delete (Cxiaoming*)fa;
system("pause");
return 0;
}
- 虚继承
#include <iostream>
using namespace std;
class CA {
public:
int a;
};
//普通继承 派生类实例包含了一份完整的基类实例数据,并简单的将派生类类成员变量添加到基类的成员变量之后
//虚继承内存布局不同于普通继承 由于指针可以是指向派生类的基类指针 而内存布局导致不能再通过声明指针类型计算偏移才引入了虚基类表指针成员
//派生类中虚基类表存储了 虚基类表指针与该类对象指针的偏移量 虚基类表指针与虚基类对象的偏移量
//用虚继承解决多继承访问不明确的问题
//菱形继承 普通继承会在CD拷贝两份CA类中的数据成员导致出现命名冲突和冗余数据的问题
//而虚继承则是让该类,承诺愿意共享它的基类 ,这个被共享的基类称为虚基类 此处为CA
//不论虚基类在继承体系下出现了多少次,在派生类中都只包含一份虚基类成员
class CB :virtual public CA{
public:
};
class CC :virtual public CA{
public:
};
class CD : public CB,public CC{
public:
};
int main(){
CD d;
d.a;
system("pause");
return 0;
}
//1)在普通继承中,如果继承的子类本身有虚函数,就会在从父类继承过来的虚表上进行扩展;
// 而在虚继承中,如果子类本身有虚函数,编译器就会为其单独生成一个虚表指针(vptr)和虚函数表,如果子类本身没有新增虚函数,那么vptr就不会存在,也不会有对应的虚函数表。
//2)普通继承中,是先按父类的方式构造对象,然后在此基础上进行扩展和覆盖;而在虚继承中,不是这样的。
//3)虚继承中,父类对象的虚表是单独保存的,通过新增的虚基类指针和虚基类表,来标明各个父类对象内存空间的偏移值。
- 异常
#include <iostream>
#include <cstdlib>
using namespace std;
void fun(int a){
if(0==a){
//异常终止函数
abort();
}
cout<<a<<endl;
}
void fun1(int a){
while(a<10){
if(5==a){
//如果函数中出现了我们规定的异常,可以使用throw抛出
//抛出对象的时候,要使用引用或者指针,不然会拷贝备份出来,即拷贝构造函数
throw a;
}
a++;
}
}
int main(){
fun(1);
//检测函数中是否有异常
try{
fun1(3);
}
//处理抛出的异常,用形参变量接收异常并处理
catch(int b){
cout<<b<<endl;
}
//多个catch类似重载的关系 根据接收的异常类型决定调用哪个catch函数处理异常
catch(char c){
cout<<c<<endl;
}
//默认catch处理
catch(...){
cout<<"default"<<endl;
}
system("pause");
return 0;
}
- 单例模式
#include <iostream>
using namespace std;
//单例模式 一个类只能创建一个对象
class CFather{
//1.私有化构造函数
private:
CFather(){
}
public:
//2.设置静态标记成员
static int flag;
//3.通过公有静态函数创建对象
static CFather* CreateOJ(){
if(flag){
//创建之后将flag标志位置为0,不允许再次创建
flag=0;
return new CFather;
}
return NULL;
}
//4.析构函数中重置标志位,以达到释放空间(delete)后可重新申请对象的目的
~CFather(){
flag=1;
}
};
int CFather::flag=1;
int main(){
CFather *pf=CFather::CreateOJ();
delete pf;
system("pause");
return 0;
}
- 内部类
#include <iostream>
using namespace std;
//外部类
class COut{
public:
int a;
//通过初始化列表给CIn对象成员传递构造函数参数COut的this指针
COut():in(this){
a=12;
}
//内部类定义
class CIn{
public:
int b;
COut *p;
//通过初始化列表内将外部类COut的this指针赋给变量p
CIn(COut *pf):p(pf){
b=13;
}
void funIn(){
//内部类访问外部类成员不可以直接访问
//可以在内部类定义一个外部类对象来访问外部类成员 但这个COut对象与主函数中声明的COut对象并不是一个对象
COut out;
cout<<out.a<<endl;
//可以在内部类,定义一个外部类的指针成员,构造函数初始化该指针,使用该指针进行访问 这样访问的对象便和主函数中的对象是同一个对象
cout<<p->a<<endl;
}
};
//内部类数据成员
CIn in;
};
int main(){
COut out;
//外部类访问内部类成员 通过外部类定义内部类对象成员,通过这个对象访问
out.in.funIn();
system("pause");
return 0;
}
- 类型转换
#include <iostream>
using namespace std;
class CFather{
public:
int a;
virtual void fun(){
}
};
class CSon : public CFather{
public:
int b;
virtual void fun(){
cout<<"cson"<<endl;
}
};
class COther{
public:
};
int main(){
CFather *p;
CSon *s;
//旧式类型转换 不考虑转换类型是否合法合理 仅仅是根据类型变换访问内存的方式
p=(CFather*)s;
//新式类型转换 类型转换运算符
//只有当前后两个类型可以相互隐式转换的时候,这个方法才是合法的
p=static_cast<CFather*>(s);
//类型转换去掉表达式的const或volatile属性,只有前后类型相同的时候才合法
const CFather *p1;
CSon *s1;
CFather* pp1=const_cast<CFather*>(p1);
//当目标类型是当前类型派生类 当目标类型是当前类型共有基类且基类是多态 当前后类型相同 类型转换才合法
CFather *p2;
CSon *s2;
p2=dynamic_cast<CFather*>(s2);
s=dynamic_cast<CSon*>(p);
//用于天生危险的类型转换 不相干类型的转换
CSon *s3;
COther *op;
op=reinterpret_cast<COther*>(s3);
system("pause");
return;
}
运算符重载
- operator
#include <iostream>
using namespace std;
class CStu{
private:
int num;
public:
int age;
int err;
CStu(int age){
this->age=age;
num=7;
err=-1;
}
//类内运算符重载
int operator+(int a){ //类内定义时当前类的对象默认是左侧的参数,所以仅需要写右侧的参数
return age+a;
}
//赋值运算符的重载必须在类内重载
int operator=(int i){
age=i;
return i;
}
//[]运算符的重载必须在类内重载
int& operator[](int i){ //可返回void*类型指针增强通用性 void是通用类型没有确定的大小
switch(i){
case 0:
return num;
case 1:
return age;
}
return err;
}
//()运算符的重载必须在类内重载 类型转换为int
operator int() const{ //()重载没有显式的返回类型,但有返回值 没有参数 不应该改变对象的内容,所以是const函数
return num;
}
//可通过友元来让重载运算符访问私有成员
friend ostream &operator<<(ostream &os,CStu &stu);
friend istream &operator>>(istream &is,CStu &stu);
};
//运算符重载 运算符重载类似于函数重载
//系统中已经有的类型运算是不允许重载的 重载的运算一定是和对象有关
void operator+(CStu &st,int i){ //将要进行运算的变量作为重载函数参数,系统会根据运算符两侧数据类型调用相应的重载函数
cout<<st.age+i;
}
//带返回值的运算符重载可以进行连续运算
//算数运算符的重载
int operator+(int i, CStu &st){
return st.age+i;
}
//比较运算符的重载 位运算符与逻辑运算符道理相同也可在类内重载
int operator>=(CStu &stu1,CStu &stu2){
return stu1.age>=stu2.age;
}
//单目运算符的重载 其他道理类似
int operator-(CStu &stu){
return -stu.age;
}
//输出运算符的重载 cout是类ostream的对象
//返回ostream可进行连续运算 来用引用传递来避免拷贝构造
ostream &operator<<(ostream &os,CStu &stu){
os<<stu.num;
return os;
}
//输入运算符的重载 cin是类istream的对象
istream &operator>>(istream &is,CStu &stu){
is>>stu.num;
//is.fail()判断赋值是否出错 出错返回值为1
if(is.fail()){
stu.num=0;
cout<<"fail"<<endl;
}
return is;
}
//复合赋值运算符的重载 通常写在类内也可以写在类外 类内可直接访问私有成员
CStu& operator+=(CStu &stu,int i){
stu.age=stu.age+i;
return stu;
}
//前置++运算符的重载
int operator++(CStu &stu){
stu.age+=1;
return stu.age;
}
//后置++运算符的重载
int operator++(CStu &stu,int i){ //int i可理解成一个标记
//后置++的本质,借助变量返回++之前的值
int a=stu.age; //也可以用变量i来接收++之前的值
stu.age+=1;
return a;
}
int main(){
CStu stu(10);
CStu stu1(11);
//相当于operator+(10,stu)+10
cout<<10+stu+10;
cout<<(stu>=stu1);
cout<<stu<<stu1;
//+=结合性从右向左
(stu+=10)+=20;
int a[10]={1,3,5};
a[1]=2;
cout<<stu[1];
stu[1]=10;
cout<<++stu;
cout<<stu++;
cout<<(int)stu;
system("pause");
return 0;
}
//类内类外运算符重载的选择:1.左操作数不是对象,类外重载 2.=、[]、()、-> 必须类内重载
//3.复合赋值运算符通常类内重载 += <<= 4.改变对象状态的运算符,如递增递减和解引用,通常类内重载
//5.算数 关系 位运算 通常类外重载
模板
- 函数模板
#include <iostream>
using namespace std;
//函数重载
void fun(int a){
cout<<a<<endl;
}
void fun(double b){
cout<<b<<endl;
}
//泛型编程思想 相同的代码实现不同的功能 多态和模板都是泛型编程思想的实现
//函数模板
//使用typename或class都可以
template<typename T,class Y> //作用域仅对下边紧挨着的代码段有效
void fun1(T a,Y b){
cout<<a<<" "<<b<<endl;
}
struct Node{
int a;
double b;
};
//函数模板
template<typename T>
void fun2(T a){
cout<<a<<endl;
}
//函数模板具体化 将一些不能通用处理的特殊类型拿出来单独处理
template<> void fun2<Node>(Node n){ //函数模板具体化优先于函数模板
cout<<n.a<<" "<<n.b<<endl;
}
void fun2(Node n){ //普通函数优先于函数模板具体化优先于函数模板
cout<<n.a<<" "<<n.b<<endl;
}
//函数模板实例化 模板函数是根据传入参数生成对应类型函数定义
//函数模板实例化是直接生成该类型的函数定义
template void fun2<int>(int i);
int main(){
fun(12);
fun(12.0);
fun1('a',12);
Node node = {12,12.12};
fun2(node);
system("pause");
return 0;
}
- 类模板
#include <iostream>
using namespace std;
//类模板 //类型默认值 默认值要从右向左连续的指定
template<typename T=char> //作用域仅对下边紧挨着的类有效
class CFather{
public:
T a;
CFather(T t){
a=t;
}
CFather(){
}
//类内声明
void show();
};
//类外定义
template<typename T>
void CFather<T>::show(){
cout<<a<<endl;
}
void CFather<int>::show(){
cout<<a<<endl;
}
int main(){
CFather<char> fa('a'); //除了类定义其他位置都需要写对应的模板参数列表 类模板可以有默认值在类模板处声明
CFather<int> ffa;
CFather<int>* pf=new CFather<int>(10);
system("pause");
return 0;
}
- 继承的模板
#include <iostream>
using namespace std;
template<typename T,typename Y>
class CFather{
public:
T a;
CFather(T t){
a=t;
}
void show(){
cout<<a<<endl;
}
};
template<typename X,typename Z>
//继承的父类模板类型可以写固定类型,也可以通过子类模板类型进行指定
class CSon:public CFather<X,Z>{
public:
//此处父类模板参数列表可以省略
CSon():CFather(12){
}
};
int main(){
CSon<int,char> son;
system("pause");
return 0;
}
- 多态的模板
#include <iostream>
using namespace std;
template<typename T,typename Y>
class CFather{
public:
virtual void fun(){
cout<<"CFather"<<endl;
}
};
template<typename X,typename Z>
class CSon:public CFather<X,Z>{
public:
virtual void fun(){
cout<<"CSon"<<endl;
}
};
int main(){
CFather<int,char> *pf=new CSon<int,char>; //指定的模板参数列表要和类定义的逻辑相通
pf->fun();
system("pause");
return 0;
}
- 类型是类的模板
#include <iostream>
using namespace std;
template<typename T>
class CA{
public:
int a;
};
template<typename T,typename Y>
class CFather{
public:
CFather(CA<char>& ca){
}
virtual void fun(){
cout<<"CFather"<<endl;
}
};
int main(){
CA<char> ca;
CFather<int,CA<char>> fa(ca);
system("pause");
return 0;
}