#include<iostream>
#include<string.h>
#include<typeinfo>
using namespace std;
/*struct Cla
{
int i;
char c;
};*/
int a(int x){
return x;
}
int func();
int main(){
func();
return 0;
}
/*void tryf(int (*a)[],int n,int m){
for(int i = 0;i<n;i++){
for(int j = 0;j<n;j++){
cout<<a[i][j]<<' ';
}
cout<<endl;
}
}
*/
int func(){
//7.1一维数组
/*
数组后面的括号是方括号,方括号内是常量表达式
定义数组的常量表达式不能是变量,数组大小不能动态定义
int i = 5;
int A[i];//老版编译器不支持这样的写法,但是GCC4.7.1之后支持这种写法
一维数组赋值:
1.A[index] = data进行赋值,通过loop进行赋值
2.以聚合方式进行赋值:int A[n] = {1,2,3,...,k}自动赋值前k个值,后面的空闲值补0
*/
//7.2二维数组
/*
二维数组初始化方式:
1.单个元素初始化
2.按顺序初始化并补零
int A[2][2] = {{1},{2,3}};//1 0 2 3
int A[2][2] = {1,2,3};//too many initializers for 'int [2]'*/
//7.3字符数组
/*
只有初始化时可以使用聚合方式赋值,也可以用双引号赋值
字符串处理函数:#include<string>
1.strcat(str1,str2)将str2的字符串接在str1后面,注意srt1的数组大小要足够放下两个字符串.虽然如果放不下虽然不会报错,但是有可能超出数组限制的范围从而导致破坏其他数据
This function or variable may be unsafe. Consider using strcat_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
2.strcpy(str1,str2)将str2的字符串复制到str1的数组中,且同时复制\0。同样如果复制的字符串大小太大会无法全部复制进去
3.strcmp(str1,str2)比较两个数的ASCII码,str1-str2返回相减的值,只有==0才相等
4.strlen(str)返回不含\0的实际长度
*/
//7.4指针
/*
指针的变量名是p *是指针运算符 &是取地址运算符
int *p = &a; 等效于 int*(p=&a)
指针运算:
p++,p--按照声明的类型向右或者向左移动一个类型单位长度
指向空的指针与指向空类型的指针:空类型:void 空对象:nullptr
指向空的指针可以接受任何类型数据的地址
//初始化:
void*p = NULL;//空对象的指针
int i = 10;
p = &i;
//使用:
//cout<<int(*p)<<endl;//error: 'void*' is not a pointer-to-object type
cout<<*(int*)p<<endl;//将void*变成int*再解引用,可以避免出错
int i = 100;
int*p = &i;
cout<<*(float*)p<<endl;//1.4013e-043 非指向空对象的指针同样有这样的强转,但是产生的是乱码,无实用效果
Cla A;
A.i = 100;
A.c = 'A';
void*p = &A;
cout<<*(int*)p<<endl;//100
//p = p++;// error: ISO C++ forbids incrementing a pointer of type 'void*'
*/
//7.4.5指向常量的指针与指针常量
/*
int const * const p
第一个const修饰int,表示指向常量;第二个const修饰*,表示指向位置不可移动
int i = 0;
const int const * p = &i;// error: duplicate 'const' int const*与
const int*是等效的,所以出现重定义错误
所以严格来说const修饰其前面的量,但是兼容const int *写法
int const i = 10;//由下面的error可知这种写法是被支持的
i = 11;//assignment of read-only variable 'i'
*/
//7.5指针与数组
/*
int *p = ...;
p[i] == *(p+i);
数组名其实是一个指针常量int *constA;
*(p--)==a[i--]
*(++p)==a[++i]
*(--p)==a[--i]
int a[2][3] = {};
cout<<typeid(a).name()<<endl;//A2_A3_i int [2][3]
cout<<typeid(a[1]).name()<<endl;//A3_i int [3]
cout<<typeid(a[1][1]).name()<<endl;//i int
指针与二维数组:
int a[2][3] = {{1,2,3},{4,5,6}};
cout<<int(a)<<endl;//6946504 首项地址
cout<<int(a+1)<<endl;//6946516 首项+12 第一行首项(从零开始) a+n第n项首地址
cout<<int(&a[1])<<endl;//6946516 同上 &a[n]第n项首地址
cout<<int(a[0]+1)<<endl;//6946508 首项+4 第1项地址(从零开始)a[n]+m第n行第m个元素
cout<<*(*(a+0)+2)<<endl<<*(a[0]+2)<<endl<<a[0][2]<<endl;//3 3 3
// *(*(a+n)+m) == *(a[n]+m) == a[n][m]
二维数组的结构:
int A[2][3] = {{1,2,3},{4,5,6}};
int *p = A[0]; //A[0]是一个一维数组,直接调用A[0]则是直接返回一维数组的首元素地址,所以A[0]是一个int*指针变量
//int *tp = A;
//cout<<*p<<endl; //error: cannot convert 'int (*)[3]' to 'int*' in initialization 由报错可知A其实是一个int (*)[3]变量(指向3个元素数组的指针)
//int**tp = A;//error: cannot convert 'int (*)[3]' to 'int**' in initialization
//cout<<*tp<<endl; A是一个int (*)[3]变量
cout<<*(++p)<<endl;//二维数组是按照行进行存储的
int A[2][3] = {{1,2,3},{4,5,6}};
int (*p)[3] = A;//一定要加上括号,因为[]的优先级高于* 这里中括号中的3是二维数组的第二个参数
//int *p[3] p是一个数组,此数组有3个元素,每个元素都是int*类型,也就是指向整型数据的指针类型。
//而int(*p)[3]中的p是一个指向数组的指针,此数组有3个int类型的元素:
//pointer to int p[3]
//理解:直接调用A时返回的是A的直接第一个元素,即是一个int[3]的数组,所以要有一个指向数组的指针进行承接
cout<<*p<<endl;//因为p是一个指向数组的指针而数组储存的是其首项的地址,所以返回的是一个地址(注:实际运行中该语句相当慢,可能编译器并不十分支持这样的操作)
int (**tp)[3] = &p;//int(**)[n]是一个指向大小为n的数组的指针的指针*/
//7.5.4指针与字符数组(字符串)
/*
char *p;
p = "hello world";
cout<<sizeof(p)<<" "<<p<<" "<<*p<<endl;//4 hello world h
字符串指针的不同输出
https://zhidao.baidu.com/question/213913940.html
定义char*p的时候只是分配了一块内存给char*,但是并没有对其指向做出限制
当p = "string"时将内存中划去一部分存储string并且返回首字符地址给char*,
因此不会误修改其他变量的内存
cout<<typeid("hello").name();//VC输出:char const [6] G++输出:A6_c 所以"string"是一个实体
//cout<<typeid(1).name();//type int
//int*p = &1;//error: lvalue required as unary '&' operand
string的特殊性在于其本身就是一个array变量可以直接做右值返回首地址
stdafx.h使用简介:
当我们使用AppWizard来自动生成某些项目的时候,系统会自动把所需要include的头文件在stdafx.h中先include一下,这样,我们只需要直接include这个stdafx.h文件即可.具体在stdafx.h中需要include什么头文件,取决于用户在AppWizard中的选择. stdafx.h是自动生成的.这就使得用户在开发中不必在每一个cpp文件中都烦琐的include头文件了,而且,维护起来也方便.
所以这个head file是VC在编译时自动生成的,为了方便用户编译,而不是具体的库
*/
//7.6指针在函数中的应用
/*
1.pass by pointer
2.pointer to function:
指向一个函数的指针称为函数指针,可以通过解引用该函数指针使用该函数
注意区分,定义指针函数(返回值为指针的函数)与定义函数指针的写法只差一个括号
int a(int x,int y){
return x+y;
}//定义一个函数
int (*p)(int,int);//定义一个函数指针 在global scope中int *p(int,int);这是函数声明
p = a;//将指针指向一个函数
cout<<(*p)(1,2);//解引用使用函数
3.通过空类型指针强转为函数指针调用函数
void*p = NULL;
p = a;//error: invalid conversion from 'int (*)(int)' to 'void*' G++不支持这样的写法,但是VC++支持
cout << ((int(*)(int))p)(2);//理解该语法:函数指针的构成为(返回类型(*)(形参类型表))故,p前面的一大串是一体的
当函数被重载的时候不能将函数名赋值给空指针,因为赋值的意义不明确
*/
//7.7指针数组&&7.8安全使用指针
/*
int*A[12];
int**p = A;
*的结合性是从右到左所以int**p也可以写成int*(*p)5
一级间址:中间使用一层指针进行间接访问
二级间址:中间有两个指针
栈:放入其中的变量大小在程序设定完之后就无法更改,其数据的消亡与产生由编译器决定
堆:通过申请的方式进行声明的变量,其大小可以根据实际运行情况在程序运行过程中动态生成,由编程者控制
动态分配与静态分配:
在使用过程中变量大小固定不可变时为静态分配
通过new delete申请动态分配变量:略
内存安全:不要访问已经被回收或者本就不属于本应用的内存地址;不要产生内存泄漏(注意:一般情况下内存泄漏是不可以通过调试发现的)
*/
//7.9引用
/*
如果不加特殊声明,一般认为引用就是左值引用
引用其实是一个隐式指针通过&操作符给变量起一个别名
type & newName = variable;
指针变量与引用的区别:
1.指针是一种数据类型,而引用不是一种数据类型而是一种操作方式
2.指针在赋值时可以将左值或者右值进行暂时性的性的改变以满足赋值需要,但是引用不行
3.引用必须在定义的时候进行初始化
右值引用:
C++11中新增加的一种引用方式
他主是要用来解决C++98/03中遇到的两个问题,第一个问题就是临时对象非必要的昂贵的拷贝操作,第二个问题是在模板函数中如何按照参数的实际类型进行转发.
左值与右值的简单区分:所有可以取地址的值都是左值,放在表达式等号右侧,在表达式完成后就被销毁的值是右值.(比如,非引用返回的临时变量、运算表达式产生的临时变量、原始字面量和lambda表达式等都是纯右值)在C++11中所有的值必属于左值、将亡值、纯右值三者之一。其中将亡值是右值引用的特点之一
如果一个右值想要被多次使用,则该右值需要被一个变量承接,于是就有了一次构造函数调用以及一次析构函数调用,而右值引用相当于不进行定义新变量而取一个变量名绑定临时变量使得临时变量的寿命增长,于是减少了一次构造调用
int&& i = 1;
i = 5;//G++不能编译,但是VC++运行正常 这里的i在VC中可以做左值 G++还没有支持这种写法
引用传参;指针传参
对于引用类型,引用必须被初始化为一个对象,并且不能使其指向其他对象,这就免去了验证参数合法性的工作
e.g.int func(int &a); 调用:func(0) 如果严格按照引用的规则就是非法的,编译器会报错,但是如果是指针则是合法的,需要程序员自己设计函数验证其合法性
但是引用又不具备指针灵活的变向能力,增加了某些代码的复杂度
数组做函数参数:使用指针:
一维数组->数组指针+数组个数
char string[] -->char *string[]
二维数组->内层数组的数组指针(内层数组个数已知的情况下)
否则:error C2036: “int (*)[]”: 未知的大小
否则使用指到最下层的指针:(int*p = &A[0][0], int n,int m)//A是int的二维数组,指向A需要int(*p)[2][3],指向A的下层数组需要int(*p)[3],指向最下层元素则直接使用int*p;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout <<*( p+(i*m+j)) << ' ';
}
cout << endl;
}
使用右值引用传递参数:将使用临时值作为函数传入参数时,应该使用右值引用进行承接.并且严格C++11标准左值引用不可以接右值数据,右值引用也不可以绑定到左值上,所以可以使用左右值引用进行重载
*/
//8.1结构体
/*
如果一种类型可以将多个数据包裹在一起,则会大大降低程序的离散型,使得程序更加符合逻辑
struct structure_name{
member_type_1 member_name1;
member_type_2 member_name2=default_value;
...
}[defined_variable_name1(optional),defined_variable_name2(optional)];
结构体赋值:如果是字符串变量则要使用strcpy来进行深度拷贝而不是直接浅拷贝
如果是在声明变量时进行初始化可以使用大括号集中赋值
特殊的声明与赋值:
struct A{
...
} vari = {1,2,3,4,...};//声明的同时进行赋值
struct A{
...
}*pointer;//同时生成结构指针变量(没有指定指针变量的指向,只是创建了一个未赋值的指针)
//p 是 nullptr 有自动初始化
结构体大小:在没有设置单位对齐下,默认结构体的大小为所有元素大小之和
注意:结构体可以在main函数中声明并且声明变量
int main(){
struct A{...}vari;
return 0;
}
*/
//8.2重命名数据类型
/*
使用typedef:
typedef ori_type New_type_name;
e.g. typedef int flag;//后面在使用int的地方都可以使用flag进行代替,相当于给int起了一个别名,可以给用户自定义的类型起别名
但是实验证明即使使用了typedef使用flag声明的变量还是int类型
typedef class zxcvbnm { int i = 0; }A1,A2;
//这里的A1,A2是别名而不是变量名,如果没有typedef才会成为变量名
同样的typedef zxcvbnm flag;依旧成立
typedef可以为复杂名称的变量取别名,方便书写
也可以调正他人设定的变量名称使其符合自己的编程习惯
注意:
typedef是存在作用域的,其作用域大小与typedef所在作用域一致
typedef少量使用有利于身心健康,但是过度使用会降低代码可读性,使得他人难以理解
*/
//8.3枚举类型的应用
/*
在事物概念中有些数据只需要离散的进行标记,则使用int就可以完成,但是在阅读代码是很难将数值与实际事物联系起来,所以产生了枚举类型,相当于对于一组常量进行起别名
enum week{monday,tuesday,wednesday,thursday,friday,saturday,sunday};
cout<<Monday<<endl;//0 enum变量默认是从0开始的一串连续的int值
week day = Monday;
enum week{a,b=3,c};//a = 0,b = 3,c = 4
enum week{monday=0,tuesday=1,wednesday=2,thursday=7,friday=4,saturday=5,sunday=6};
不能用整数给枚举变量赋值 但是可以加上enum的赋值:day = (enum weekday)3;//使用强转格式
枚举变量的运算:跟整形一样可以比较return bool value以及加减乘除运算结果都是int型
tyepdef 与 enum区别:
typedef是将一个类包装成另一个名字,而enum是将字符形式的名称隐式转换成int值
*/
//8.4类型推导
/*
有时候很难确定某个函数在某种该条件下的返回值类型,因为它可能使用了模板的方式进行定义,这个时候可以使用自动类型auto进行自动判断与承接
e.g.int i = 10;
auto j = i;//编译器会自动识别i的类型,并且将j设置为相同类型
auto在函数返回值中的应用:类型推导
auto相当于:
Type k = somevalue; //将k设定为somevalue对应的type
decltype(k) p = somevalue; //使用k声明变量
注意:auto的数据必须初始化,auto不能用于数组,不能用于形参
*/
//8.5结构体与函数-8.6结构体数组
/*
使用结构体做函数参数可以减少参数的个数使得代码更简洁
最常使用的是结构体指针,在节省代码量同时减少空间的开销
结构体数组,通过正常的数组声明进行定义
同样可以使用指针访问结构体数组
*/
//8.7共用体
/*
将被互不交叉使用的数据都存放到一个内存地址,有效的减少内存消耗,减少大量临时变量对内存占用
格式:
union name_of_union{
//name_of_union 可以为空,但是必须要在定义的同时声明变量,否则后面没有办法声明
type name_of_variable;
......
}union_variable;
注意:union不是一个数据类型只是一个关键词,name_of_union也不是一个数据类型,只可以引用name_of_union的成员(使用成员也是使用.进行访问)
共用体的大小:其成员变量中最大的那一个,每一次对于共用体进行赋值都会覆盖上一次赋值的内容
共用体特点:一个时间只能存入唯一一个变量,每一次访问只有最后一次输入的对象有效,共用体的地址与其成员变量的地址是同一个,不能直接对共用体变量名赋值与调用,不能时使用共用体变量名作为函数参数
*/
//8.10使用宏定义替换复杂的数据
/*
#define命令是定义一个可替换的宏,提供了一种替换源代码中字符串的预处理机制
#define分为不带参数的宏与带参数的宏两种
1.不带参数的宏定义:
#define 宏名 字符串
每当遇到标识符时就用后面的字符串替换,需要改变一个常量的时候就只用改变对应的常量数值即可,减少了改变的代码数量,宏名简单明确增加代码可读性,一般宏名习惯性用大写字母表示
注意:宏定义不是C语句,不需要在结尾的地方打分号.
宏定义完成之后可以像变量一样成为其他宏定义的一部分:
#define i 1
#define n 2*i
注意:宏替换的是标识符 而不是字符串,所以如果是字符串中出现标识符是不会替换的
1.如果宏定义的字符串长于一行,可以在该行末尾用\续行
2.#define出现在函数外面,作用域是标注地点到该源文件结尾(一般实际编程中会将define放在文件头或者独立的文件中)
3.可以用#undef终止宏定义的作用域
4.宏定义区别于变量只是做字符替换,不分配内存空间
2.带参数的宏定义
#define 宏名称(参数表) 字符串
e.g.#define MUL(x,y) ((x)*(y))
//这里最好加上括号因为如果x,y不是一个数而是一个表达式的时候则有可能因为操作符相互影响而导致计算错误
//另外替换名于参数之间不可以加上空格因为会导致编译器将变量当作替换目标处理导致替换出错
相当于一个小型的函数定义,用一个式子替换将要使用的地方
调用:
a = MUL(i,j);
宏替换增加了代码速度因为不存在函数调用但是代价是程序会变长
*/
return 0;
}
菜鸟 C++ learning 2
最新推荐文章于 2023-02-27 08:25:32 发布