C基础加强
参考资料:b站Av19301027
- 数组名字作为参数
数组作为函数参数,退化为一个指针
void fun(int a[6]) 其中6可以是任何正整数或者直接不写,没有任何意义,因为这里只是将a作为一个数组起始地址(不管是多长的数组),然后退化为一个指针。
- 数组名字
首先它是代表整个数组的,所以&数组名字会得到数组的地址,&数组+1会在数组的基础上增加整个数组内存长度,这个时候数组名字跟int a; 中的a是一样的,是一个变量名字,只不过是一个数组的名字,不是地址,sizeof(数组名字)得到数组大小也是这个道理,另外数组名字是个常量,不能buff=0x1111;接下来讲一个特例,就是单独打印a的时候,a代表数组第一个元素的地址,数组名字+1也是这样的。
#include
#include
using namespace std;
int main(int argv, char** argc) {
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
cout << "a:" << a << endl;
cout<< "&a" << &a << endl;
cout << "a+1:" << a+1 << endl;
cout << "&a+1" << &a+1 << endl;
system("pause");
}
- 定义数组类型——数组也是一种数据类型
typedef char A[10];
A buff;
//buff相当于char buff[10];
- 二维数组
char a[3];
char aa[3][30];
将二维数组看作一维数组的数组,一维数组是基本类型的数组,因此a表示首元素(char)的地址(char * ),a+1的步长为1,aa也表示首元素(一维数组char xxx[30])的地址,我们前面知道一维数组的步长为数组的长度*类型长度。故二维数组名字是第一行(一维数组)的地址; 首行地址和首元素的地址是一样的,但其步长不一样。
#include
#include
using namespace std;
int main(int argv, char** argc) {char buff[3][30] = {"aaaaaa","bbbbb","cccccc"};
//相差90,&buff代表整个二维数组的地址,因此步长是整个二维数组
cout << "&buff:" << &buff << endl;
cout << "&buff + 1:" << &buff + 1<< endl;
//相差30,buff是第一个数组(二维数组看作是多个数组)的地址,因此步长是一个一维数组的大小,30
cout << "&buff:" << buff << endl;
cout << "&buff + 1:" << buff + 1<< endl;
system("pause");
}
再看一个例子,更好理解,有时数组名是数组名,有时是地址。
下面打印出来的是第二行首地址,因为a这里是地址,不是数组名,因为这里有加1,数组名是没有加1的。加1之后得到第二行的地址(也就是&buff),接着加* 得到buff(这里要将a+1看做一个第二行的地址,这里的第二行要看做一个整体,看做某种类型的变量,a+1不是int类型的指针,虽然跟第二个行收个元素(int类型)的地址一样)),我们在一维数组中知道,buff和&buff实际上值是一样的,一个代表一维数组首元素地址,一个代表一维数组的地址。
#include
#include
using namespace std;
int main(int argv, char** argc) {
int buff[4] = {0};
int a[3][4] = { 0 };
//buff
cout << "*(a+1) = " << *(a + 1) << endl;
//&buff
cout << "(a+1) = " << (a + 1) << endl;
system("pause");
}
那我们怎么得到第二行第二个元素地址和内容呢?因为上面+1都是一个一维数组一个一维数组地跳,a+1是第二个一维数组的地址,a[1](也就是 * (a+1))是第二个一维数组的名字也是首地址,所以a[1]+1也就是第二行第二个元素地址,a[1][1]或者* (a[1]+1)就是第二行第二个元素。
下面两种赋值哪种是正确的,第一种,首先p是一个大小为4的int数组的指针,而前面我们说过a刚好是第一行的地址(一维数组的地址),所以第一种,要令第二种正确,p应该怎么样定义呢?int (* p)[3][4]; 因为a是一个二维数组的地址。
还要注意p+1的跨度为4*4=16
#include
#include
using namespace std;
int main(int argv, char** argc) {
int a[3][4] = { 0 };
int (*p)[4];
p = a;
p = &a;
system("pause");
}
- 二维数组求元素个数,行数,列数
#include
#include
using namespace std;
int main(int argv, char** argc) {
int a[3][4] = { 0 };
//二维数组元素个数
cout << "元素个数:sizeof(a)/sizeof(int) = " << sizeof(a) / sizeof(int) << endl;
cout << "元素个数:sizeof(a)/sizeof(a[0][0]) = " << sizeof(a) / sizeof(int) << endl;
//二维数组有多少行
cout << "行数:sizeof(a)/sizeof(a[0]) = " << sizeof(a) / sizeof(a[0]) << endl;
//二维数组有多少列
cout << "列数:sizeof(a[0])/sizeof(int) = " << sizeof(a[0]) / sizeof(int) << endl;
cout << "列数:sizeof(a[0])/sizeof(a[0][0]) = " << sizeof(a[0]) / sizeof(a[0][0]) << endl;
system("pause");
}
- 二维数组做形参
数组做形参都会退化为指针,但步长不一样
#include
#include
using namespace std;
//退化为指针,但步长仍然是4
void printArray(int a[3][4]) {
for (int i = 0;
i < 3;
i++) {
for (int j = 0;
j < 4;
j++) {
cout << a[i][j] << " ";
}
cout <
- 指针长度
无论什么类型指针,系统位数相同就是一样的
- 结构体类型和对应变量定义
如果不加上typedef就可以在结构体变量定义的时候不加struct。
- void类型
void a; 不行,因为不知道要给a分配多少个字节
void *p; 可以,因为指针的字节在系统位数固定的时候也是固定的
所以——数据类型的本质是固定内存块大小的别名
- 最好的学习方法——重复
- 固定写法
文章图片
- 字符串赋值的问题
如果是用数组的形式去获取字符串常量的值,那么会新建一个字符串数组,然后将字符串数组赋给变量str,而p跟q的值一样是因为字符串常量一样的时候其地址也是一样的。
#include
#include
using namespace std;
void str() {
char str[] = "123";
char *p = "123";
char *q = "123";
if (str == p) {
cout << "str==p:true" << endl;
}
else {
// 打印这个
cout << "str==p:false" << endl;
}if (q == p) {
//打印这个
cout << "q==p:true" << endl;
}
else {
cout << "q==p:false" << endl;
}
}
- 间接赋值是指针存在的最大意义
- 需要用到二级指针的场合
修改普通变量需要一级指针,修改一级指针(也是变量)需要二级指针
#include
#include
using namespace std;
void changeSecondPtr(int *p) {
p = (int *)0x2222;
}void changeSecondPtr2(int **p) {
*p = (int *)0x3333;
}int main(int argv, char** argc) {
int *p = (int *)0x1111;
//只传一级指针改变不了指针的值
changeSecondPtr(p);
cout << "p: " << p << endl;
//改变得了指针的值
changeSecondPtr2(&p);
cout << "p: " << p << endl;
system("pause");
}
- 指针在子函数中注意事项
1.判断形参指针是否为NULL
2.最好不要直接使用形参
#include
#include
using namespace std;
int myStrcpy(char *src, char *dst) {
if (src =https://www.it610.com/article/= NULL || dst == NULL) {
return -1;
}while (*dst++ = *src++);
//这一行会出现问题,因为dst已经不是首地址了
cout <<"myStrcpy:" << dst << endl;
return 0;
}int myStrcpy2(char *src, char *dst) {
if (src =https://www.it610.com/article/= NULL || dst == NULL) {
return -1;
}char *from = src;
char *to = dst;
while (*to++ = *from++);
cout <<"myStrcpy2:" << dst << endl;
return 0;
}int main(int argv, char** argc) {
char str1[] = "abc";
char str2[10];
char str3[10];
myStrcpy(str1, str2);
myStrcpy2(str1, str3);
system("pause");
}
- const和指针
不看类型,
若直接修饰是 * ,那么指向的内容不能改变,指向可以改变。
若直接修饰是p,那么指向的内容可以改变,指向不能改变。
可以简单记为*代表其内容,p代表指针,const修饰到的就不能改变。
#include
#include
using namespace std;
int main(int argv, char** argc) {
char var;
char buf[] = "abcd";
const char *p1 = buf;
char const *p2 = buf;
p1[0] = 'f';
//error!
p1 = &var;
//okchar * const p3 = buf;
p3[0] = 'f';
//ok
p3 = &var;
//error!const char *const p4 = buf;
p4 = &var;
//error!
p4[0] = 'f';
//error!system("pause");
}
- C语言的const是个冒牌货
可以通过指针间接修改变量的值,C++就避免了
int main(int argv, char** argc) {
const int a = 5;
int *p = &a;
*p = 6;
}
- 二级指针做输入的三种模型
第一种模型:之前变量要在子函数中改变,那么要传指针进去,现在字符串(字符指针)变量要改变,那么就要传二级指针进去。
eg:char *a[] = {"1111","2222","3333"}(a是指针数组,见下面)要进行排序,那么函数在写的时候应该是这样sort(char **p)
第二种模型:跟第一种差不多,只不过是char aa[3][30]={"1111","2222","3333"}; ,排序函数要这样写,sort(char buff[][30]),不能写乘sort(char **p),因为会退化为指针,排序的时候我们还是要30个30个就像一维数组那样子来。
第三种模型:第一种分配在全局区域,第二种分配在内存区域,也就是栈,第三种是堆
- 指针数组
//[]的优先级要比高,我们首先看[]知道是个数组,接着char只不过是一个类型
char *strs1[] = {"111","222","333"};
记住下面,其实就是数组的逻辑
sizeof(strs1) == 12
sizeof(strs1[0]) == 4
跟char **strs2的区别,strs2是一个指针,不能赋值{"111","222","333"},而strs1是一个数组,但strs2可以赋值一个指针数组元素的指针(指针的指针),通常是第一个
- 数组指针
数组指针的步长是整个数组大小,具体解释和格式看下面,记住两者的区别。
//数组指针类型,对比int a[10];
中a是一个数组,相当于说是int [10]给中间的东西
//套上了一个数组类型,int [10]就类似于int等基本类型,现在int (*P)[10]就相当
//于int *p,所以下面语句中,P是一个指针类型,一个指向数组的指针类型。
typedef int (*P)[10];
//指针数组类型
int a, b;
typedef int *T[10];
T buff = { &a,&b };
- 结构体偏移地址trick
#include
#include
using namespace std;
typedef struct Student {
char name[50];
int age;
} Student;
int main(int argv, char** argc) {
int pianyiliang = (int)&(((Student *)(0))->age);
cout << pianyiliang << endl;
system("pause");
}
- 结构体内存对齐
空间换取时间,如果没有对齐,需要读取多次,然后拼接,对齐只需要读取一次
...
- 函数指针定义
应用:函数调用,回调函数
#include
#include
using namespace std;
void justPrint(int input) {
printf("=============%d\n", input);
}int main(int argv, char** argc) {typedef void FUN(int input);
FUN *p1 = justPrint;
p1(1);
typedef void (*PFUN)(int input);
PFUN p2 = justPrint;
p2(2);
void (*p3)(int a) = justPrint;
p3(3);
system("pause");
}
- 函数指针数组
fun先和[]结合得到数组,其他都是函数指针类型的写法。
#include
#include
using namespace std;
void justPrint(int input) {
printf("=============%d\n", input);
}int main(int argv, char** argc) {void (*fun[5])(int input);
fun[0] = justPrint;
system("pause");
}
- .c->.exe
预处理:
编译:语法检查,编译成汇编文件
汇编:汇编文件变目标文件——二进制文件,.o
链接: 结合多个.o文件编程.exe文件
- include
“”会优先查找本地,再查找系统
<>只会查找系统
- 【C基础加强】define
推荐阅读
- Python基础|Python基础 - 练习1
- Java|Java基础——数组
- Java基础-高级特性-枚举实现状态机
- 营养基础学20180331(课间随笔)??
- iOS面试题--基础
- HTML基础--基本概念--跟着李南江学编程
- typeScript入门基础介绍
- c++基础概念笔记
- 集体释放
- 自我修养--基础知识